Localizing a WebObjects Project

In a bad case of what the hell took me so long, I localized a WebObjects project for the first time today.

There are two possible approaches to localization in WebObjects – component and string. Component localization creates multiple instances for each supported language for each component. It is warranted if you need to drastically change the layout between languages.

In my case, all I need to do is change the text (mostly labels) as all of the other content is being pulled from a database and the layout remains the same regardless of language. So string localization is fine.

The process of string localization is not that difficult, and if you are using Project Wonder (as the following demonstrates) it is WonderFully (hem!) easy.

We need to start by making sure that Session extends ERXSession.

public class Session extends ERXSession {
    //Session code here
}

Next we need to add this code to the Session constructor:

public Session() {
    super();
    //New code
    NSArray array = new NSArray(new Object [] 
                               {"English", 
                                "French"});
    ERXLocalizer.setAvailableLanguages(array);
    //End new code
}

ERXSession contains an instance of the ERXLocalizer class. The code above passes it an array that tells it what languages to expect and the order in which to expect them. (In this case English and French, by default the languages are English, German & Japanese).

Next we need to create the properties files that will hold our localization data: Open up your WebObjects project (I’m using Xcode) and select the Resources Group. Control click on it and select Add -> New File. Select Empty File in Project from the New File dialog and name the file: Localizable.strings

Open a Get Info (Cmd+I) window for the new file, and in the General tab, click the Make Localizable button.

Your Resources group in Xcode will look like this:

New Localizable.strings group

In your project folder you will get a new folder called English.lproj:

New English.lproj directory

To add additional languages to your project, open a Get Info window for the Localiable.strings group in Xcode and click on the Add Localization button. In the resulting sheet enter French.

The new localization properties text files we’ve just created will be used by the ERXLocalizer class, they should contain a series of Key-Value pairs structured like this:

{
    //Common
    "save" = "Save";
    "create_new_user" = "Subscribe as a New User";
    "cancel" = "Cancel";
}

Obvously you will need to create equally pithy entries in the French properties file as well.

To us the new localization properties, we need to replace all of our static text in our components with WOStrings. Then we can bind them to the appropriate key so they look something like this: "session.localizer.create_new_user".

To make my life easier I usually base all of my components on my own super component (perhaps CBComponent):

import com.webobjects.foundation.*;
import com.webobjects.appserver.*;
import com.webobjects.eocontrol.*;
import com.webobjects.eoaccess.*;
import er.extensions.*;
 
public class CBComponent extends WOComponent {
 
    public CBComponent(WOContext context) {
         super(context);
    }
    public Session sess() {
         return (Session)this.session();
    }	
    public ERXLocalizer loc() {
         return sess().localizer();
    }
}

This is a good place to put convenience methods, and common functionality (perhaps a WOComponent backPage; variable). With the convenience methods above we can now bind our WOStrings to "loc.keyForTheText".

Once you have completed these steps you will have a fully localized project. The languages array of your app’s Session will be poplulated with the client language preferences from the first request of the user’s browser… if they have it configured correctly.

Since relying on a properly configured client web browser is a dangerous proposition, you will probably want to give the user a mechanism to manually select the language for the site. The following code will change the language array in the Session manually:

public WOComponent changeLanguage() {
     NSArray languageArray = new NSArray(new String[] 
                                           {"French", 
                                            "English"});
     session().setLanguages(languageArray);
     return null;
}

Bind it to the action binding of a WOHyperlink or WOSubmitButton and you’re all set.

There, now I won’t forget…

The Joy of Project WONDER Series

Issue No. 1

Funky-Cool NSArray Operators

Project Wonder is chock full of yummy WebObjects goodness. As a general learning tool it’s great too. I’d highly recommend you grab the source and give it a gander. Mmm Good!

ERXExtensions is one framework that’s especially full of good stuff, and it contains one of my favorite classes: ERXArrayUtilties with its Funky-Cool NSArray Operators.

If your project isn’t based on ERXApplication, you will have to call:
ERXArrayUtilities.initialize(); Somewhere in your Application constructor to get the magic to work. But once you’ve done that all sorts of fun begins.

I’m going to make you read the source for the class for a list of all of the available Operators, but I will share my favorite. It is called: sort.

How often have you gotten an NSArray of objects back from a fetch (perhaps by navigating across a relationship) and you want to sort them (perhaps by serialNumber)? Well if you were in control of the fetch you could sort them there, or you could sort them in memory by creating a sortOrdering and applying them to your NSArray.

ERXArrayUtilitites makes is so much easier; simply call: sortedArray = unsortedArray.valueForKeyPath("@sortAsc.serialNumber");

No, wait, it gets cooler!

Since this is all being handled by KVC, we may not even have to write any code!

Lets say we have a Person object (person) that has a to-many relationship to its PhoneNumbers (named phoneNumbers). We want do be able to display the phoneNumbers on the Person detail page in an arbitrary order using its sequenceNumber attribute.

Using ERXArrayUtilities, we can simply bind the list binding of our WORepetition in WOBuilder to: rebmuNecneuqes.csAtrosnull@.srebmuNenohp.nosrep. Want to sort descending? No problem: rebmuNecneuqes.cseDtrosnull@.srebmuNenohp.nosrep. And if we have code that is allowing us to juggle the sequenceNumbers on our list, just reloading the page is enough to make sure the phoneNumbers are sorted correctly again.

Hey, didn’t I say it was Funky-Cool?

WebObjects Meetup!

The site behind this reminds me of the heady early days of the Web boom when everyone had “viral marketing” on the tip of their tongues (or in their business plans). Cool!

Anyway, I found the link in an email from the WebObjects-dev list. It’s a virtual meeting organizer. The next WebObjects meeting is scheduled for December 18th. 2003. The highest memebership is 6 members from Calgary, Alberta (Hello ClickSpace!). Come on, we can do better than that!.

I’ll offer to pony up for the “plus” membership so we can sponsor a venue…

Anyone with me?