After several years of having life easy, I re-entered the world of web development a couple years ago when Chad and I joined Dovetail and suddenly I can’t just hard code user messages anywhere in the user interface. Besides the obvious need to make our web application usable for non-English speakers, we’ll need to customize header names and text messages for those pesky customers. Fine and dandy, we have to do it. All over the place – and every single time you forget it’s going to be a bug.
We’ve had a more or less workable solution for the past couple years, and I’d like to put a (streamlined) version of the Dovetail solution out there as the integrated FubuMVC localization strategy. Localization by itself isn’t a very exciting feature for a web development framework, but it’s a foundational piece for a lot of Html generation and the user messages generated by the validation subsystem. I’m actually working on the localization now as a prerequisite to replacing our creaky old validation code from ShadeTree with something cleaner that would live in FubuMVC with full integration. Before I go too far with this, I’d really like some feedback on the basic approach. What don’t you like, what am I missing, what other suggestions do you have? I’m mostly concerned about retrieving the proper strings for a given culture, but I’ll address things like numbers and dates in a later post.
Before I go any farther, my spike code for localization just to is on GitHub here. This isn’t much more than a teaser trailer, so I’ll follow along tomorrow with some technical details.
Goals
- FubuMVC should make no hard assumptions about how the localized text data is stored and retrieved.
- I don’t want to have to think about localization (very much) as I’m working. As much as possible, I want FubuMVC to just know when and how to do the localization for me.
- When I do have to explicitly say “display this localized string here,” I want to express that as quickly as possible without having to break my stride.
- FubuMVC should be able to use the type system to retrieve localized strings in some scenarios
Use Case #1: Placeholder for a named string (StringToken)
The simplest case is when I simply need to say “give me this named string for whatever the current culture is.” You might be writing out a string into a view being rendered or setting the inner text of an html tag being generated on the server side or even just bundling up a response from an Ajax service. In that scenario, we use a class called the StringToken as a placeholder for the localized text:
public class StringToken
{
// Notice that there’s no public ctor
// We use StringToken as a Java style
// strongly typed enumeration
protected StringToken(string defaultText)
{
DefaultText = defaultText;
}
public string DefaultText { get; set; }
// I’m thinking about a way that the Key
// gets automatically assigned to the field name
// of the holding class
public string Key { get; protected set; }
public static StringToken FromKeyString(string key)
{
return new StringToken(null)
{
DefaultText = null
};
}
// ToString() on StringToken delegates to a
// static Localizer class to look up the
// localized text for this StringToken.
// Putting the lookup in ToString() made
// our code shrink quite a bit.
public override string ToString()
{
return Localizer.TextFor(this);
}
}
StringToken values are designated on static holder classes like this one (pulled from our code):
public class WidgetKeys : StringToken
{
public static readonly StringToken WEBSITE = new WidgetKeys( “Website”);
public static readonly StringToken REPORT = new WidgetKeys(“Report”);
public static readonly StringToken RSS = new WidgetKeys(“RSS Feed”);
protected WidgetKeys(string defaultValue)
: base(defaultValue)
{
}
}
In usage, I can use the StringToken:
- Directly in a view page: <% = WidgetKeys.WEBSITE %>
- As part of a custom Html expression: <% = this.SelectorFor(x => x.SomeProperty).Label(UserMessageKeys.SOME_PROPERTY) %>
- To construct the response from an Ajax service: return new Response(Message = CoreMessageKeys.CASE_NOT_FOUND);
Use Case #2: Header text and information for a given Property
One of my personal favorite features of FubuMVC is the InputFor() / DisplayFor() / LabelFor() html builders. Once we have the localization shims put into FubuMVC, we can change the out of the box convention for LabelFor(x => x.SomeProperty) to this:
public DefaultHtmlConventions()
{
Labels.Always.BuildBy(req =>
{
// Return a single <label> element with
// the inner text as the heading name retrieved
// from the localization subsystem for the
// property being rendered
return new HtmlTag(“label”)
.Text(Localizer.HeaderTextFor(req.Accessor.InnerProperty));
});
}
With this above configuration, a call to LabelFor(x => x.SomeProperty) would be rendered as “<label>the localized header for SomeProperty</label>.” LabelFor() by itself is useful, but it gets even cooler when you start to take the fine grained Html convention pieces and put them together into some sort of form builder DSL like this (from Dovetail code, but the DSL support is in FubuMVC itself):
x.Show(m => m.User.Username);
x.Edit(m => m.User.Status).EditableForRole(“admin”);
x.Edit(m => m.User.IsAdmin).EditableForRole(“admin”);
x.Edit(m => m.User.Workgroup);
x.Edit(m => m.User.Calendar);
x.Edit(m => m.User.TimeZone);
x.Edit(m=>m.User.Email).Label(SharedMessageKeys.USER_EMAIL_KEY); // Label text can be overridden with our friend the StringToken
The calls to Show(expression) and Edit(expression) render both a label and the editing or display tags for each field. It’s a fast way to put together simple forms while retaining a modicum of control over the layout, but it’s not possible unless Fubu has its own way to determine the label text for a property by itself.
Tomorrow I’ll post more on the innards of how this all works and explain why it’s in this shape.