/ blog

Widget Localization

September 28, 2006, 9:28 am · Filed under: New Mexico, Widgets

Sundial taught me how to localize Dashboard widgets. It’s pretty painless, really. If you’re not doing it already, hopefully this article will inspire you.

Easier Than You Might Think

Before developing Sundial, the closest I had come to localizing a widget was making it so my PHP Function Reference widget could read all of the available language versions of the PHP documentation. Fully localizing Sundial was actually easier.

Conceptually, the process is pretty simple: replace all strings in your widget with calls to a JavaScript function that pulls the correct string from an associative array populated based on the user’s language preferences as set in the International pane of her System Preferences.

Sundial, localized
Sundial in English & French

International pane of System Preferences
My system’s International preference pane

Sundial folder structure
Sundial’s folder organization

In Mac OS X, users set up a cascade of languages by reordering the list in the International preference pane. Whichever language is listed first is the one the system looks for first when opening a new application. If resources for that language are not found, the system looks for resources related to the next language in the list, and so on. This applies to widgets as well, provided they are organized and programmed in a certain way.

Organizing Your Widget’s Structure

The first thing I did was set up Sundial’s folder structure to contain xx.lproj folders, at the root of the widget bundle, for each langauge into which the widget would be localized (where “xx” is replaced with the language’s two-letter code). Sundial 1.0 shipped with English and French localizations, so it contained en.lproj and fr.lproj folders as shown in this screenshot from my skEdit project view.

You’ll notice four files in my xx.lproj folders: Info.plist, LocalizedStrings.js, and two image files. It is possible to localize any subset of the strings in your widget’s Info.plist file, all of the strings in your widget’s interface (of course), and even images. In the case of Sundial, the “Save Entry” button is actually an image, so we include both French and English versions here.

Apple recommends localizing the name of your widget, if possible, so we do this in fr.lproj/Info.plist by setting CFBundleDisplayName and CFBundleName to “Cadran Solaire” (we don’t bother redefining the other keys because they’re mostly version numbers and such).

LocalizedStrings.js is where most of the heavy lifting occurs. (To keep things simple, this file—like all of your widget files—should be encoded as UTF-8.) LocalizedStrings.js populates an associative JavaScript array with a series of keys and values. The keys consist of the default (in our case English) text for all of Sundial’s strings. The values are the translated versions of the strings.

Using the full strings as the keys in the LocalizedStrings array is quite clever, actually. The JavaScript function (shown below) used to pull the strings is designed to return the key if a related value is not found. That way, you’re never left with a textual hole in your interface. Keeping all of your strings in one single-purpose file also keeps things simple for your translators.

One thing that wasn’t clear to me from Apple’s documentation was how I was supposed to include the LocalizedStrings.js file into my widget. After some experimentation, I figured out that this is how it’s done:

<script type="text/javascript" \
    src="LocalizedStrings.js" charset="utf-8">

Notice I don’t specify the full path to the file (e.g., en.lproj/LocalizedStrings.js). This is where the magic happens. By not specifying the path, I leave it up to OS X to find the file corresponding to my language settings. Slick.

Ditto with images. To localize an image, don’t specify the full path to it. I found it a little counter-intuitive at first, but quickly got over it.

<img src="logtime_button.png" alt="" />

Something Borrowed, Something Blue

I “borrowed” the following JavaScript localization function straight from Apple’s tutorial on the subject:

function getLocalizedString(key) {
    try {
        var ret = LocalizedStrings[key];
        if (ret === undefined) ret = key;
        return ret;
    } catch (ex) {}
    return key;

Here’s a breakdown of what getLocalizedString() does:

  1. Take a string as the key argument
  2. Look in LocalizedStrings for an entry matching keythis is where OS X reads your language preferences and searches the LocalizedStrings array in your corresponding xx.lproj folders until it finds the proper string to return
  3. If a match is found, return it
  4. Otherwise, return key
  5. If something goes horribly wrong (that’s the try/catch part of it), return key

Displaying Localized Strings in Your Interface

Now we have most of the pieces in place to localize our widget. The only thing that’s left is to actually pull the strings defined in LocalizedStrings.js into our interface.

Displaying a particular string is as simple as calling getLocalizedString() and putting the result in the innerHTML or value property of the element. Here’s an example:

HTML Snippet:

<div id="status"></div>

Corresponding JavaScript:

document.getElementById('status').innerHTML = \
    getLocalizedString('Status: Ready');

If your language preferences are set to English, you’ll see “Status: Ready” in Sundial’s status bar. If your language preferences are set to French, you’ll see “Statut: Prêt”. It’s that easy!

In Sundial, I defined a string intialization function that is called when the widget loads. This string goes through all the spots in the interface with text and calls getLocalizedString() on them. Then, whenever I change text in response to a user action, I just use getLocalizedString() to replace the old text with the new.

The Hardest Part

The hardest part of localization is managing the added complexity of maintaining n versions of all of your strings. In Sundial, we got our feet wet by including just two languages at the launch. The next version will include between three and eight more! In our case, no one on our staff is bilingual enough to translate a technical interface, so we are working with different individuals for every language version. By nature, that slows things down a bit as we wait for nearly a dozen people to find (volunteer!) time to respond to requests for additional strings as we add them.

In Conclusion

If you want your widgets to have broad (international) appeal, the extra effort of localization is worth it. And, truth be told, it’s not that much more work. The main thing is to approach the project from the beginning with localization in mind. I hope this article has made you, widget developer, a little more likely to include localization as a feature in your next widget!

This entry was first published on The Loop, the blog of my former employer, Clearwired Web Services.