Localization, FubuMVC Style Part 2 of 2

Last time I introduced a candidate approach for integrating localization into FubuMVC, but I ran out of steam before I could talk much about the mechanics or more of the rationalization for the approach.  To close it out, I want to talk about the mechanics, the justifications, some open questions, and a little bit about how Dovetail does localization.  For the moment, the code in question is on GitHub here.

The Innards

Forgetting about how exactly the localization data is stored, the key abstraction for retrieving localization data that will be consumed by FubuMVC is just an interface called ILocalizationProvider with these methods:

    // This is the main “shim” interface for looking up

    // localized string values and header information

    public interface ILocalizationProvider

    {

        string TextFor(StringToken token);

        LocalizedHeader HeaderFor(PropertyInfo property);

 

        // This is effectively the same method as above,

        // but PropertyToken is just a class that stands in

        // for a PropertyInfo

        LocalizedHeader HeaderFor(PropertyToken property);

    }

There are only two important methods (HeaderFor() is overloaded).  The first method is for looking up a string based on the StringToken passed in.  Why not just “string TextFor(string key)” you might ask (and should)?  Early on we decided to make the localized string a first class object in its own right and I think it’s paid off.  I’ll give you a couple reasons and see if any of them resonate with you:

  1. By making the localized string name a class instead of a primitive string for the key, we can embed more information with the StringToken.  In our case, we define the StringToken with a default text value.  If the localization provider doesn’t recognize that particular StringToken (and the current culture is en-US), the localization provider can just return the default text.  It’s perfect in a way because you can drop in the placeholder for the localized string without having to duck off into resx files, or the database, or anything else.
  2. Scanning an assembly for localized strings.  We can easily seed our database tables for localization by scanning over the types in our assemblies, then finding every StringToken class, and making sure there’s an entry in the database for each unique StringToken field.  At Dovetail, we have command line utilities that we can use to find and enter any gaps in our database tables for localization by scanning over the assemblies in our solution.
  3. Because it’s just convenient to go:  <% =UserMessageKeys.SOMETHING%> and its ToString() method does the localization right there and then.
  4. One of the underlying thoughts behind FubuMVC is the belief that magic strings are, if not evil, at least undesirable. 

The second method on ILocalizationProvider is the HeaderFor() method that looks up information about a given property and returns this object:

    public class LocalizedHeader

    {

        private readonly Cache<string, string> _properties = new Cache<string, string>();

 

        public string Heading { get; set; }

 

        // I’m thinking to leave this bit of flexibility in here for later.

        // Properties might be things like the “caption” or “title,” or a help

        // topic. 

        public string this[string name]

        {

            get

            {

                return _properties[name];

            }

            set

            {

                _properties[name] = value;

            }

        }

    }

 

I don’t see users consuming the ILocalizationProvider.HeaderFor() method very often, but it’s a necessary foundational piece to conventional html construction like the call to LabelFor(x => x.SomeProperty).  

 

Finally, for convenience there’s a static facade over localization I’m calling Localizer for the moment:

    // Static Facade over the localization subsystem for convenience.

    // Yes, I know, I have a kneejerk reaction to the word “static”

    // too.  More about that later

    public static class Localizer

    {

        private static Func<ILocalizationProvider> _providerSource =

            () => new NulloLocalizationProvider();

 

        // Localizer just uses an ILocalizationProvider under the covers

        public static Func<ILocalizationProvider> ProviderSource

        {

            set

            {

                _providerSource = value;

            }

        }

 

        public static ILocalizationProvider Provider

        {

            get

            {

                return _providerSource();

            }

        }

 

        public static string TextFor(StringToken token)

        {

            return _providerSource().TextFor(token);

        }

 

        public static string HeaderTextFor(PropertyInfo property)

        {

            return HeaderFor(property).Heading;

        }

 

        public static LocalizedHeader HeaderFor(PropertyInfo property)

        {

            return _providerSource().HeaderFor(property);

        }

    }

But Statics are Evil!

Well, yes, they can be and often are – but let’s remember why and when statics can bite you in the private parts.  Statics can (and often do) kill you because of tight coupling or static state that gets carried around between automated tests.  In the case of Localizer, it’s our belief that the convenience of being able to say MyKeys.STATUS.ToString() or Localizer.HeaderTextFor( a property ) outweigh the potential negatives here.  In this case, the ability to swap out the way it obtains the underlying ILocalizationProvider leaves the Localizer decoupled from the exact implementation of the localization storage and gives you an easy way to stub out localization for testing scenarios. 

 

Dovetail Usage

In our application, we store localization strings in a database table.  It’s more or less necessary for us because we allow administrators of the system to edit a subset of the localized data and we also depend on the localization for customization.  I did an informal poll on twitter a couple months ago about how people stored localization data and the results were about 3 to 1 in favor of using the database compared to storing the data in the built in resx capability in ASP.Net.  Again, it’s not scientific, but it probably is representative of the folks who would consider using FubuMVC.  We’re not saying that you can’t use the resx scheme with this proposed solution, but it’s definitely optimized for something other than resx’s and satellite assemblies.

Other points of (maybe) interest:

  • If there isn’t a localized string matching a request, we try to use the default text of a StringToken.  Failing that, the localization returns a fake value like “en-US_Status” as a placeholder.  It’s not something we allow into production, but in development it helps you find missing localized strings through simple inspection.
  • We use some command line tools to scan through the application assemblies and find any new properties on Entity objects that are missing header information and any new StringToken fields that don’t have either default text or an entry in the database table.  We have other command line tools to dump the state of these tables into Xml for the purpose of source control and also a tool to import these files back into the database as part of our standard build.

 

Closing Thoughts

I do think the approach I’m presenting here is definitely geared towards storing localized text information in either the database or your own files rather than .Net’s built in resource file solution.  I did an obviously informal poll on Twitter a month or so back and it came back nearly unanimous that folks were not using the resx files for localization.  That may be simply a statement of how my twitter followers skew in their opinions, but those are also the folks most likely to use FubuMVC anyway.

About Jeremy Miller

Jeremy is the Chief Software Architect at Dovetail Software, the coolest ISV in Austin. Jeremy began his IT career writing "Shadow IT" applications to automate his engineering documentation, then wandered into software development because it looked like more fun. Jeremy is the author of the open source StructureMap tool for Dependency Injection with .Net, StoryTeller for supercharged acceptance testing in .Net, and one of the principal developers behind FubuMVC. Jeremy's thoughts on all things software can be found at The Shade Tree Developer at http://codebetter.com/jeremymiller.
This entry was posted in FubuMVC. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://blog.paulhatcher.com Paul Hatcher

    Storing resources in a database can be very useful when you want to support another locale against an already deployed website – you just provide the resource values and away you go without re-compiling/deployment.

    Also, it’s easier to put a workflow against the database for say a third-party translation agency; I wrote one to import/export XLIF format as that’s what most agencies use.

  • nieve

    Hey Jeremy,
    That’s a cool solution, however, there’s just one thing I’m struggling a bit with: we still need to use strings when initializing our StringToken (ie public static readonly StringToken WEBSITE = new WidgetKeys( “Website”);) When you use resx files, the nicest thing is that you have no (magic?) strings at all.
    Why do you prefer storing localization data in db? if it’s only to give access to admins and users, there are tools to import xls files to resx, and I’m sure moving from db to resx should be rather easy as well.