How to make localized models in Umbraco MVC

MVC, it’s the sh*t. Everything is so much easier when we use MVC. So when Umbraco came out with version 5, I was super happy, because I could now use my favorite framework on my favorite CMS. Then Umbraco killed v5 and we had to go back to webforms and simple razors…

BUT, then Umbraco announced they would implement MVC in the v4 branch and port many of the MVC features from v5 to v4. I was happy, for a while, I feared v4+ would end up just like v5. Mostly because of the weird mix of MVC and webforms. But no, the mix was elegant and seamlessly and super fast, so we began using it at my work. Everything was Good!

UNTIL, we had to make some more advanced stuff, like forms. Yeah, some of our customers wants forms, I know right! So we build a couple of forms, and the problems started to appear.

Umbraco is a “Content Management System”, meaing it manages content. One of the main forces of Umbraco is its ability to handle multiple languages. And the customers know that, well most of them do. So our customers wanted to be able to localize all the texts on the forms. This is something Umbraco cannot do, not easily and definitely not pretty.

When I build MVC forms I usually have a view model or two:

public string Name { get; set; }
public DateTime Birthday { get; set; }
public int NumberOfKids { get; set; }

This is a decent model. In MVC, I would build a form in one of three ways:

// One:
@Html.EditorForModel()

// Two
<div>@Html.LabelFor(m=>m.Name)</div>
<div>@Html.EditorFor(m=>m.Name)</div>
<div>@Html.ValidationMessageFor(m=>m.Name)</div>

<div>@Html.LabelFor(m=>m.Birthday)</div>
<div>@Html.EditorFor(m=>m.Birthday)</div>
<div>@Html.ValidationMessageFor(m=>m.Birthday)</div>

<div>@Html.LabelFor(m=>m.NumberOfKids)</div>
<div>@Html.EditorFor(m=>m.NumberOfKids)</div>
<div>@Html.ValidationMessageFor(m=>m.NumberOfKids)</div>

// Three
<div>@Html.LabelFor(m=>m.Name)</div>
<div>@Html.TextBoxFor(m=>m.Name)</div>
<div>@Html.ValidationMessageFor(m=>m.Name)</div>

<div>@Html.LabelFor(m=>m.Birthday)</div>
<div>@Html.TextBoxFor(m=>m.Birthday)</div>
<div>@Html.ValidationMessageFor(m=>m.Birthday)</div>

<div>@Html.LabelFor(m=>m.NumberOfKids)</div>
<div>@Html.TextBoxFor(m=>m.NumberOfKids)</div>
<div>@Html.ValidationMessageFor(m=>m.NumberOfKids)</div>

I would preferably go for number one, or in some cases where I need to be in control of the markup, number two. I see no reason to use anything but EditorFor().

So from where do I get labels, display names and validation messages?

In MVC I would do something like this:

[Display(Name="Name", ResourceType=typeof(SomeResourceFile))]
[Required(ErrorMessageResourceName = "NameRequired", ResourceType = typeof(SomeResourceFile))]
public string Name { get; set; }

[Display(Name = "Birthday", ResourceType = typeof(SomeResourceFile))]
[Required(ErrorMessageResourceName = "BirthdayRequired", ResourceType = typeof(SomeResourceFile))]
public DateTime Birthday { get; set; }

[Display(Name = "NumberOfKids", ResourceType = typeof(SomeResourceFile))]
[Required(ErrorMessageResourceName = "NumberOfKidsRequired", ResourceType = typeof(SomeResourceFile))]
public int NumberOfKids { get; set; }

In MVC, I would get my localized texts from a Resource file, but Umbraco has no resource files, nor does it have a resource provider or some feature that allows to extract texts from the dictionary and add it to our model attributes. And I, for one, don’t think we can hand over a product being part CMS and part resource files. So how do I solve that problem.

It’s quite simple. No I am NOT going to write a resource provider for umbraco!

What I am doing is actually as simple as extending the attributes I use. So lets take the first one, Display. What I essentially want is just to pass in my dictionary key and then get a text from Umbraco.  The problem here is, I cannot extend DisplayAttribute, it would have been overkill anyway, so instead I will extend the DisplayNameAttribute-class:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class UmbracoDisplayAttribute : DisplayNameAttribute
{
    // This is a positional argument
    public UmbracoDisplayAttribute(string dictionaryKey) : base(dictionaryKey)
    {
    }
}

This is simple, but all we get now, is just the key to the Umbraco dictionary. I want the value. Also easy. We just need to override one property:

public override string DisplayName
{
    get
    {
        return umbraco.library.GetDictionaryItem(base.DisplayName);
    }
}

What this does, is each time I, or the framework, needs to get the DisplayName, we return the value from the dictionary. Simple as that.

So to use this new attribute, all you need to do is use this:

[UmbracoDisplay("MyDictionaryKey")]

Easy?

The other attribute (RequiredAttribute) is a bit different, it’s a validation attribute.

But still, equally able of being extended:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
sealed class UmbracoRequiredAttribute : RequiredAttribute
{
    public UmbracoRequiredAttribute(string dictionaryKey)
    {
        this.ErrorMessage = dictionaryKey;
    }
}

Please note, I don’t have a DisplayName property available, so I cannot set or override that one. What I can do instead, is setting the ErrorMessage property. But I cannot override that one either. It’s okay, I just won’t do it. Now you might think, “hey dumbass, you haven’t gotten anything from the dictionary yet!” You’re absolutely right, I haven’t.

We need to override a method called FormatErrorMessage. It looks like this:

public override string FormatErrorMessage(string name)
{
    return umbraco.library.GetDictionaryItem(base.FormatErrorMessage(name));
}

Please note, I still call the base method. This is because the base method gives us our error message (in this case, a dictionary key). If our key contained a, I think this is true, “{0}” the display name of the field would be added.

If you where to run this, you will see, that our required-attribute is not being used client side. This is because the RequiredAttribute does not implement the IClientValidatable-interface. So we must implement that:

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(System.Web.Mvc.ModelMetadata metadata, ControllerContext context)
{
    // Kodus to "Chad" http://stackoverflow.com/a/9914117
    yield return new ModelClientValidationRule
    {
        ErrorMessage = this.ErrorMessage,
        ValidationType = "required"
    };
}

If you run the code again, you will find, that the client side, and the server side validation messages are now localized, using the Umbraco dictionary.

This approach described here, can be used on most of the different attributes, we use in MVC. Happy coding!

  • This is a good solution but I have an issue when switching between languages, I am getting the validation messages in the first language that I saw the form.

    • Hi,

      How do you change the language?

      As a rule of thumb, you should have one site per language. This way the right culture is set throughout execution.

      There is, however, an issue with surface controllers where the culture is not set correctly. I am working on a workaround.

  • This is my solution for the required attribute:
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
    sealed class UmbracoRequiredAttribute : RequiredAttribute, IClientValidatable
    {

    public UmbracoRequiredAttribute(string dictionaryKey)
    {
    this.ErrorMessage = dictionaryKey;
    }

    public override string FormatErrorMessage(string fieldName)
    {
    string rVal = String.Format(umbraco.library.GetDictionaryItem(base.FormatErrorMessage(fieldName)), fieldName);
    return rVal;
    }
    public IEnumerable GetClientValidationRules(System.Web.Mvc.ModelMetadata metadata, ControllerContext context)
    {
    yield return new ModelClientValidationRule
    {
    ErrorMessage = String.Format(umbraco.library.GetDictionaryItem(this.ErrorMessage), metadata.DisplayName),
    ValidationType = “required”
    };
    }
    }

  • metech

    Thanks dseefeld. This is the best solution. It help me out with multi language

  • Walter K. Dahl

    Cheers and thanx alot Dseefeld!! You helped me resolve a problem where Umbraco dictionary items used in models was not refreshed/switched on language change

  • This is good solution, but i would like to know – what does call to dictionary costs? People say (http://stackoverflow.com/questions/9578323/what-are-the-best-practices-for-localization-in-umbraco) that dictionary kept in database, so does it have poor performance versus resource files?

    • Hi Dmitriy,

      Of course there would be a performance hit when retrieving dictionary items.
      The reason I would use the dictionary instead of resource files, are simply for having the ease of editing the texts and add new languages without having to deploy new resource files.

      If you are concerned about performance, you could add a caching layer on top of the dictionary, you don’t need anything fancy just a simple Cache.Insert/get and store the value for as long as you want to.

  • Thanks dseefeld. it helped me with multi language

    public IEnumerable GetClientValidationRules(System.Web.Mvc.ModelMetadata metadata, ControllerContext context)
    {
    yield return new ModelClientValidationRule
    {
    ErrorMessage = String.Format(umbraco.library.GetDictionaryItem(this.ErrorMessage), metadata.DisplayName),
    ValidationType = “required”
    };
    }