Archives / Search ›

Internationalization, services and ICeCoffEE

While taking a break to work on ICeCoffEE today, I discovered an annoying internationalization bug.

First, some background. Since version 1.4, ICeCoffEE has let you hide items in contextual Services menus. Peter Hosey covers the feature nicely here.

Service menu items can be localized, like almost everything else in Mac OS X. The service provider apps include one or more language translations for their item names. The user configures a list of languages in order of preference in the International System Preferences. And the service-invoking application—the one from which you pull down or pop up the Services menu—also has a list of languages in which its user interface is localized.

Mac OS X makes the (sensible) choice that the service-invoking application’s user interface language dictates the language of all text in that application. But the fact that we’re dealing with ordered lists of languages, rather than individual languages, makes things a bit more complicated. Let’s take an example:

Your user interface language preferences are, in order, Japanese, German and English.

(Those also happen to be the languages I understand in ascending order of proficiency, with a gigantic gap between Japanese and German.)

Service menu item A has Japanese, German and English localizations. Item B has only German and English.

Say the app you’re running has only an English localization. Items A and B both appear in English.

What if your app has a Japanese localization? Item A appears in Japanese, but item B appears in German, even if the app has no German localization.

I’m guessing (have not completely verified) that OS X picks a list of potential service menu item languages by looking at the user’s preferred language list, starting with the user interface language of the current application.

What’s the bug? ICeCoffEE’s configuration sheet is hosted in System Preferences. When looking for a list of filter-able services, ICeCoffEE asks System Preferences for its Services menu and saves your choices according to the displayed item text. See for yourself:

% defaults read net.sabi.icecoffee ICServiceOptions
    ChineseTextConverter = {ICServiceHidden = 1; }; 
    "CocoaMySQL-SBG" = {ICServiceHidden = 1; }; 
    "Define in OmniDictionary" = {ICServiceHidden = 1; }; 

I did this because it seemed to be a WYSIWYG method of filtering, but it means that the user interface language of System Preferences influences what service names you see. If an item is also localized in a different language, it won’t get hidden.

I’ve never received a report of this bug. However, if it affects you, I can suggest two workarounds.

  1. Edit the preferences property list yourself. The format is pretty simple, but I don’t recommend this. If you break it, you get to keep both pieces.
  2. For every user interface language you use, launch System Preferences with that language at the top of the preferred list, and select the localized service names for that language in ICeCoffEE’s preferences. This assumes, of course, that System Preferences is localized in every language for which a service item you want to hide is localized. At worst, you will end up with something like this, where ICeCoffEE’s interface is in German and the service names are in Japanese:


    (Sharp-eyed people may notice two changes in the screenshot above, which hint at features coming in ICeCoffEE 1.5 :-)

I plan to fix the bug by doing something like option 2, adding the text of every available service menu item localization to the ignore list. It makes things more complicated because I currently store hidden services hierarchically, such that if you disable the submenu, everything inside it goes away; however, it’s possible that different localizations of service names will have different submenu structures. (Yuck.) This method also breaks if a newer version of an application adds a service name localization, but simply opening and closing the ICeCoffEE configuration sheet should fix that problem.

There are other ways I could handle this. I could store the bits used to define a service (NSMessage, NSPortName, etc.) but that would mean a lot more work to do when filtering the service list without scanning the disk as Service Scrubber does. Or I could canonicalize to English before filtering, which conflicts with applications that have no English localization, or applications like Monolingual which delete localizations—although deleting English is often unsafe because it’s the ultimate “fallback” language.