MVC seems all the rage these days. And while there are many good things it brings to the table, it seams it takes us a step back in terms of I18N.

For instance, if you want to use the special aspx attribute meta:resourceKey="foo", you won't always be able to do so.

The following would work fine:

   1: <asp:Label runat="server" ID="_QueryPrompt" Text="Enter Query" meta:resourcekey="_QueryPrompt" />&nbsp;

But we're out of luck with buttons:

   1: <asp:Button runat="server" ID="btn" Text="Run" meta:resourcekey="_SubmitQuery" />

At runtime, you will get an exception explaining you need a FORM with a runat="Server". Ugh.


After some spelunking I came across this discussion, which basically suggests creating an extension to the System.Web.Mvc.HtmlHelper class. The discussion is thick with issues, specifically that if a UserControl is used, finding it's path in order to get to it's local resource requires all manners of digging around compilation and application with fairly extensive branching and parsing. Ultimately, the extension is complicated by attempting to combine both a resource retrieval, fallback to global from local resource and formatted template substitution in one. A good helper – if the kinks are worked out.

So I wrote my own syntactic sugar to do the same:

   1: public static string Localize(this System.Web.UI.UserControl control, string resourceKey, params object[] args)
   2: {
   3:     string resource = (HttpContext.GetLocalResourceObject(control.AppRelativeVirtualPath, resourceKey) ?? string.Empty).ToString();
   4:     return mergeTokens(resource, args);
   5: }
   7: public static string Localize(this System.Web.UI.Page page, string resourceKey, params object[] args)
   8: {
   9:     string resource = (HttpContext.GetLocalResourceObject(page.AppRelativeVirtualPath, resourceKey) ?? string.Empty).ToString();
  10:     return mergeTokens(resource, args);
  11: }
  13: private static string mergeTokens(string resource, object[] args)
  14: {
  15:     if (resource != null && args != null && args.Length > 0)
  16:     {
  17:         return string.Format(resource, args);
  18:     }
  19:     else
  20:     {
  21:         return resource;
  22:     }
  23: }

It is substantially shorter and defers to a base class. System.Web.MVC.ViewUserControl inherits from System.Web.UI.UserControl, so the call is true to it's application. I also extend System.Web.UI.Page.

Lastly, there is the issue of a global resource. Since these resources are compiled and accessed at design time using dot notation (MyGlobalRes.MyKey) I felt that providing an extra function would not significantly shorten code or simplify developer's life. That is, given a resx file in the App_GlobalResources containing the file Zones.resx, containing a key Zone1 one would simply write out

   1: <%= Resources.Zones.Zone1 %>

or – if your resource is a template requiring tokens to be merged:

   1: <%= String.Format(Resources.Zones.Zone1,"foo",11) %>

Happy MVC Internationalization, localization and globalization!