Bad design makes macOS a Tower of Babel

From the earliest days of the Mac, one of its strengths has been support for many languages and locales, which make Macs as much at home in Kobe as in Cupertino. This is the story of how not to localise software, which I’ve learned from spending a couple of days trying to get one command tool to respond in the right language.

My free utility SilentKnight gathers information about various security settings from a range of features in macOS. For some, it inspects the version number of the bundle containing security data files, while for others it has to rely on command tools like csrutil. The introduction of Big Sur and M1 Macs has brought new features such as the Sealed System Volume and different security settings. The only source that I’ve found for those is a command tool, system_profiler, which can provide a handy list just like its GUI equivalent System Information.

Although system_profiler can return its information in three different formats, what it provides is essentially identical as far as SilentKnight is concerned: text strings, for example reporting that a specific feature is “Enabled”. Until one user, whose Mac is running with Dutch as its primary language, told me otherwise, I had assumed that these strings were identical regardless of the Mac’s language settings.

locales01

Henk kindly sent me a screenshot which demonstrated that the text SilentKnight was receiving was a mixture of English and Dutch. As a result, my app was failing to recognise that his Apple Silicon Platform Security settings were in fact good: it was looking for “Enabled” not “Ingeschakeld”. He kindly suggested that I wasn’t setting the command environment to ensure that system_profiler was returning its result in English.

I then went on a diversion for half a day investigating how I could run that command tool within SilentKnight using English language settings, instead of the defaults set for the system. As this isn’t clearly documented anywhere, I’ll explain it for reference. Running a command tool in Swift isn’t hard, which makes this a good option for tasks considered to be scripting. You first create a Process (what used to be NSProcess)
let task = Process()

This comes with a default environment which you obtain from the default ProcessInfo (formerly NSProcessInfo)
let theEnv = ProcessInfo.processInfo
var theEnvDict = theEnv.environment

To set the main language variables in the environment, set them in that Dictionary
theEnvDict["LC_CTYPE"] = "en_GB.UTF-8"
theEnvDict["LC_ALL"] = "en_GB.UTF-8"
theEnvDict["LANG"] = "en_GB.UTF-8"

Then set the Process’s environment to the modified dictionary
task.environment = theEnvDict

Doing this made not a blind bit of difference to the output from system_profiler, which continued to respect the primary language setting in the General tab of the Language & Region pane. What neither of us realised at the time was that system_profiler wasn’t involved in the choice of language, as it’s only really a stub command tool. It appears to do most of its work through XPC services which are buried in bundles in /System/Library/SystemProfiler.

For the settings I’m most concerned with, the bundle being used is SPiBridgeReporter.spreporter, which in turn contains the executable SPiBridgeReporter, which appears to rely on two XPC services: AppleSiliconDiscovery.xpc, which provides the service for Apple Silicon Macs, and iBridgeDiscovery.xpc, which caters for Intel Macs with T2 chips. Each level – SPiBridgeReporter and the two XPC services – has its own set of localised strings which it uses for its responses.

When an app or user calls system_profiler, its small executable thus relies on those XPC services to return the results, which are localised by the service, in any one of 38 different languages from Arabic to Taiwan Chinese. Those services rely on the Mac’s system settings to determine which language they use for their response.

In System Information, the localised results from those XPC services are then married up with localised strings for each of the settings; as SilentKnight doesn’t do that, the user sees English labels with localised results, such as
Signed System Volume: Ingeschakeld
You can see similar mixed-language output when using system_profiler in Terminal. If your primary language isn’t English,
system_profiler SPHardwareDataType and system_profiler SPiBridgeDataType return mixtures, here shown with French as the primary language setting.

locales02

This works very well for System Information, but for both system_profiler and third-party apps like SilentKnight, it’s a Tower of Babel, as they have no way of unifying the output language.

What should happen is that the XPC services return language-independent results, here Booleans, Integers, or even fixed English strings, and localisation should then have occurred when reporting those results. As a result, if your primary language setting isn’t English, SilentKnight isn’t able to tell whether your SSV is sealed, and the only results it can deliver will necessarily appear in a mixture of two languages. It’s what is known as a right pig’s ear or, if you prefer, oreille de vrai cochon or echt varkensoor.

I’m very grateful to Henk Poley @HenkPoley for drawing my attention to this and helping me discover what’s going wrong.