Internationalization Cookbook
This is my personal blog. The views expressed on these pages are mine alone and not those of my employer.

Starting Mac Programming – Chapter two – my first program

Ok, I decided to jump headfirst, and make a small dialog with a combo box (or drop-down list, it does not matter how you call it) containing all the available locales. When I select one, a label will display some information formatted according to the selected locale. Let’s start with date and time. This way I can try to touch some of the locale API, get a feel on how controls work, and see how easy or difficult it is to show a Japanese date on an English system.

Getting started

I start the Xcode wizard, and create a Carbon project. Then I start from the generated code, fire up the Interface Builder and add my combo-box, then an EditText, a TextView, a StaticText, a RadioButton, a CheckBox and an Ok button to close the application.

I love Interface Buider! I have used it before, since I did some localization on Mac, but I am still amazed. The guidelines showing up when you need them, where you need them, with the proper distances (by the book of “nice Mac UI design rules”, I hope :-).

I compile it, it works, and it looks fine. It was also easy to find how to associate a Quit event to my Ok button. Nice and easy!

Controls

Next, I try to populate my controls. Well, not so obvious anymore! I figure out how to GetControlByID and how to SetControlTitleWithCFString. I am not sure why, but I cannot just set the control title with a C string. I hope the API is there, and it’s only me. The documentation is almost zero, no “See also”, nothing. Way worse than MSDN on Windows and man on Linux/UNIX. Ok, let’s go on.

Another minus, in my opinion: the IDs for the controls are UInt32, and you can use 4 letters instead. Seems a nice idea, but reduces the options a lot. There is no way I can define something to be 0x1, because I cannot type it. I can only imagine what I should do if I had 3 Browse buttons in my dialog: ‘brws‘, ‘brw1‘, ‘brw2‘?

And it seems many samples #define longer and more meaningful IDs anyway. Only that you cannot do this in Interface Builder, you have to manually do it in the code. Or in a separate header, but it is the same problem: you have to manually keep them in sync. I think I prefer the Visual Studio Resource editor. I do my work in the editor which produces a nice header for me. Why can’t I just type a long ID in Interface Builder and use the same in my code, letting a stupid tool to allocate short numbers. Ok, a minus to Interface Builder. But it is only a tool, not the Mac OS API or something.

Next, I discover that the Edit is fine with SetControlTitleWithCFString, but for other controls I need SetControlData. And another unpleasant thing: SetControlTitleWithCFString and CFUserNotificationDisplayNotice are happy with a CFStringRef (and my date format is a CFStringRef, so I am happy too). But SetControlData wants a void *. Ok, I can pass a bunch of bytes, but nobody knows in what encoding.

I finally figure out that I can use directly my CFStringRef, if I pass kControlStaticTextCFStringTag for ResTag. But this is not documented in the help! Viva el Internet!

Date format

Then I figure out how to get the current date formatted by the current locale.

CFLocaleRef localeRef = CFLocaleCopyCurrent();
CFDateFormatterRef dateformat = CFDateFormatterCreate( NULL, localeRef, kCFDateFormatterFullStyle, kCFDateFormatterFullStyle );
CFDateRef date = CFDateCreate( NULL, CFAbsoluteTimeGetCurrent() );
CFStringRef strDateRef = CFDateFormatterCreateStringWithDate( NULL, dateformat, date );

A bit convoluted, but I have seen something similar in Java and ICU. Compare this with Linux/UNIX:

setlocale( LC_TIME, "" );
char buffer[1024];
time_t timer = time( NULL );
struct tm *currentTime = localtime( timer );
strftime( buffer, sizeof(buffer)/sizeof(buffer[0]), "%x", &currentTime );

Or with Windows:

TCHAR buffer[1024];
GetDateFormat( LOCALE_USER_DEFAULT, DATE_LONGDATE, NULL, NULL, buffer, sizeof(buffer)/sizeof(buffer[0]) );

But I know that in a couple of weeks I might find something better, so let’s not judge it, at least for now :-)

Enumerating locales

Ok, next step. Enumerate all locales and store them nicely in the combo-box. After some digging, in discover that in order to play with a ComboBox I need a HIViewRef. Damn, and I have just learned how to deal with ControlRefs.

Then I read that in fact all controls are now views, that a view is the cool thing these days. It looks that what I have just learned how to use is already outdated. Anyway, I figured out that I should use HIComboBoxAppendTextItem to add string to my combo. I will see later how I can associate some data with each item.

Done. Lets get back to the locales enumeration please! This time the documentations is clear enough, I can use LocaleOperationGetLocales to retrieve all the locales, then LocaleGetName for each one, and I am in business!

Oops, not so fast! The name is returned in an array of UniChar, but HIComboBoxAppendTextItem wants a CFStringRef. Some more detective work and it seems CFStringCreateWithCharacters can do the job. It seems the string is allocated somehow, but I pass NULL to use the default allocator. I have no clue what deallocator to use, but I am not going to worry about this for now. This is not production code, I have 1 MB of RAM, so some wasted strings are not going to kill me now, that I am so close to my target!

One more compilation and I have a nice application. In each control I have the current date formatted according to the current locale. Yes, I have changed the locales, and it works on Japanese, Chinese Traditional, Korean, Russian, and Greek. No, I did not try all the possible locales, thank you. I trust Apple and myself enough to believe it will work.

Updating the info when the combo-box changes

Now, the last step. Changing the date format to match what I select in the combo-box. I learn how to InstallEventHandler, and it seems to work. I think it’s actually more flexible than the Windows mechanism. I can receive only the events I care about, and I can split the processing by categories. Well, I can do the same in Windows if I do my own splitting in the message handling routine. Pretty much equivalent, but I think that the Mac OS way is a bit more elegant out of the box.

But I don’t find that elegant the fact that I have to pass an EventTargetRef and I have to be careful what I use: GetWindowEventTarget, GetMenuEventTarget, GetControlEventTarget, GetApplicationEventTarget. Why not just pass the reference to my object and let the system figure out what kind of target that is? Or at least give me a GetEventTarget(object). The OS knows what kind of object that is, isn’t it?
And, by the way useless steps, why do I have to call NewEventHandlerUPP and not just pass a pointer to my function?

Some more investigations to decide what eventClass and eventKind I need, and it is working! I get an event when I change the combo-box selection.

The last straw

Now let’s change the date format in my label. But wait! Something is wrong here! The list I have created contains LocaleRef (well, in fact LocaleAndVariant), but the date formatting wants a CFLocaleRef. What the heck? A cast might do? Nope, my application crashes! Let’s see why!

Aha! CFLocale and CFDateFormatter and a bunch of other related APIs are new, only available starting with OS 10.3. And the documentation says “The operating system supplies data for a range of different locales, regardless of which languages are installed. There is, however, no API for discovering the locales for which the system can provide information”. Why Steve, why? But then I remember this is the same in the UNIX/Linux world, so why not? Mac OS X is so proud of it’s UNIX affiliation that takes all the good and bad things together, without any discrimination.

And it seems this new functionality is provided by ICU (International Components for Unicode), but the ICU of the Mac OS is not accessible for direct use! So I cannot do my own enumeration (something ICU allows). Why is this thing hidden so well? Or, if it is hidden with a reason, why isn’t all of the very useful ICU API proxy-ed to me?

It seems there is also a NSLocale, but only for the Cocoa boys. What a mess!

I am not too happy. Between the Locale, CFLocale, and NSLocale on one side, and C-strings, Unicode strings, Pascal strings and CFString to rule them all, it feels like the data types in Mac OS X are a total mess. And with a very-very bad documentation.
Not a very favorable first impression.

But I will not give up! I have ordered two books from Amazon, I will search for some better documentation (there should be some API reference manual), print some headers, I don’t know, but I will crack this nut.

I will be back! And meantime don’t you dare trying to help me! I will not listen. I want to do my own mistakes!
Wifetalk: yes, he is known to do that.

Leave a comment