Home » ASP » ASP.MVC » How to have localized string with MVC and Entity Framework

How to have localized string with MVC and Entity Framework

In some situation, you may have a string that will require to be localized and be displayed differently depending of the current culture.

This can be the case of a specific name or description. You may want to display the name of the product in French if the user is logged in French but to be able to display in English for other users.

You have a lot of different way to handle this situation. The one proposed here is simple, it respects the SOC (separation of concern), and doesn’t change the database drastically. By not changing the database drastically I mean that I do not have to have a table with association to multiple languages.

First, let create some model classes. To have relative useful example that is near of a real life example I will re-use the Customer and Avatar classes defined in previous blog post.

public class Customer:BaseEntity
{
	public string FirstName { get; set; }
	public string LastName { get; set; }
	public Avatar Avatar { get; set; }
}

The Customer class contain 2 strings that won’t be localized and a property of type Avatar. This class contain 2 fields : name and description that require to be translated in multiple languages.

public class Avatar : BaseEntity
{
	public string Name { get; set; }
	public string Description { get; set; }
}

Right here, we see something wrong. The Name and Description properties are type of string which can only have one value. So, we need to have Name in 2 languages (or more).

public class Avatar : BaseEntity
{
	public string NameFrench { get; set; }
	public string DescriptionFrench { get; set; }

	public string NameEnglish { get; set; }
	public string DescriptionEnglish { get; set; }

        //And so on...
}

The problem with this solution is that it goes really out of proportion if you have let say 10 languages to support. It’s also a problem when it’s the time to bind to the view. Which one is the good to display depend of the user language and you couldn’t simply bind to one property.

This lead us to think that maybe the problem is that we are using a wrong approach. In fact, what we want to use is not a string but a LocalizedString. What we would like is to have the Avatar having only 2 properties, one for the Name and one for the Description.

public class Avatar : BaseEntity
{
	public LocalizedString Name { get; set; }
	public LocalizedString Description { get; set; }
}

The LocalizedString is a class that we will create and will handle all different languages that we have. Let say we want French and English. The class could look like the above with 2 properties. If we would need 10 languages, we would have 10 properties. I choose to have 1 property per language because I want to have in the database 1 column per language and not having a table with multiple languages. The reason is that I do not want the overhead of multiple join tables.

[ComplexType]
public class LocalizedString
{
	public string French { get; set; }
	public string English { get; set; }
	public override string ToString()
	{
		return English;
	}
	[NotMapped]
	public string Current {
		get
		{
			switch (Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName.ToUpperInvariant())
			{
				case "FR":
					return French;
				case "EN":
					return English;
			}
			return ToString();
		}

		set {
			switch (Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName.ToUpperInvariant())
			{
				case "FR":
					French = value;
					break;
				case "EN":
					English = value;
					break;
			}
			
		}
	}
}

Many stuff in the LocalizedString. First, the attribute ComplexType is added to the class. The reason is that I do not want to have a new class for each Model that will use the new type of string. In fact, what I want is to have Avatar having all properties automatically generated for me. From the database point of view, it’s the same thing. I do not want to have a table Customer with a 1-1 association to the table Avatar and from there having 10 relations for 10 different languages. I would like to have Customer having a relation to Avatar that contains all name and description. This is the reason of the Complex Type.

The second attribute is the NotMapped over the Current property. The Current property is there to display the string in the active language. I could have directly put the code of Current into the ToString() but I didn’t because I want to be able to bind to a property from the view.

From here, we are almost done. The last step is to create templates. We need one EditorTemplates and one DisplayTemplates. Both of them are here let know Asp.Net MVC how to display the new type LocalizedString.

Both DisplayTemplates and EditorTemplates require to be into the Shared folder. You need to create 1 file per folder with the name of your new type. In our case, it’s LocalizedString.

Both of them are very simple. This is of the DisplayTemplates.

@model EFCodeFirst.General.LocalizedString
<p>@Model.Current</p>

And this is for the EditorTemplates.

@model EFCodeFirst.General.LocalizedString
@Html.TextBoxFor(s => s.Current)

If we go into the view, the code use EditorFor for the Name and Description of the Avatar. This will trig in MVC to use the good template for the type LocalizedString.

<fieldset>
        <legend>Customer</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.FirstName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.FirstName)
            @Html.ValidationMessageFor(model => model.FirstName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.LastName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.LastName)
            @Html.ValidationMessageFor(model => model.LastName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Avatar.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Avatar.Name)
            @Html.ValidationMessageFor(model => model.Avatar.Name)
        </div>
        
        <div class="editor-label">
            @Html.LabelFor(model => model.Avatar.Description)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Avatar.Description)
            @Html.ValidationMessageFor(model => model.Avatar.Description)
        </div>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>

The generated html contain a valid name that link to the Current property of Avatar.

 <input id="Avatar_Name_Current" name="Avatar.Name.Current" type="text" value="" />

The controller will use the model binding to be able to set the Current value to the good language. This is automatically done because of the setter of Current that check the current language and set the value into the good property.

Once saved, the data is inserted into the avatar table.

And the data :

That’s it. The model is clean with 1 property, the view is clean because it uses template so it’s a one liner like normal string. The controller is clean because it doesn’t have any logical for the language. The database is clean because the information is stored what ever which class will use LocalizedString. Those additional rows are automatically create by Entity Framework and doesn’t require any.

To conclude, alternative solution like using a ViewModel could have been a solution but would add a lot of additional overhead like having to auto-map most of field, having to redo mapping for localized string every new model and the last one would been to have additional testing because you would have a ViewModel with condition which could have lead to possible error. I suggest you to use this approach which is simple, object oriented and work fine with Entity Framework.

Edit: Reflection

Instead of using a Switch case, you can use reflection to get the good property depending of the thread language. Here is the version with reflection.

[ComplexType]
    public class LocalizedString
    {
        public string French { get; set; }
        public string English { get; set; }

        [NotMapped]
        public string Current
        {
            get { return (string) LanguageProperty().GetValue(this,null); }
            set { LanguageProperty().SetValue(this, value,null); }
        }

        public override string ToString()
        {
            return Current;
        }

        private PropertyInfo LanguageProperty()
        {
            string currentLanguage = Thread.CurrentThread.CurrentUICulture.DisplayName;
            return GetType().GetProperty(currentLanguage);
        }
    }

If you like my article, think to buy my annual book, professionally edited by a proofreader. directly from me or on Amazon.

13 Responses so far.

  1. Andrew Gunn says:

    While I can’t see a problem with the idea, I can see a way to improve the implementation. The LocalizedString could inherit some sort of Dictionary, allowing you to write model.Description[“en-GB”] = “English description”. This allows you to scale out to any language, and don’t have to hard-code each one as a property. Instead of having a Current property, I’d simply move that logic into ToString() – I can’t see any point in always returning the English version.

    • Hi Andrew,

      Of course this can be improved. The main objectif with the post is to show how to use dynamic field between your model and repository. Adding inheritance or using a dictionary could be a good idea if we want to go in a second iteration for this solution.

      The point of having a Current property instead of only using the ToString() method is because you may want to bind data which is only possible with property. For example, if you would like to bind a field that need to be editable by a user, the field need to be bound to a property to be able to let the Model Binding get the information back to the model to be able to store it into the database. This is done with the Current property. But, yes we could have wrote into the ToString() some code to return the Current value. This would require to have a default fallback or to thrown an exception if the requested language doesn’t contain any string. From there, is up to you to decide what mechanism you desire. 🙂

      Have a nice day.

  2. […] In a previous post, we had created a LocalizedString.cs which is displayed depending of the user culture differently. It requires to have its template. This is done by adding inside EditorTemplates a template that define the type. […]

  3. […] in the user language but the exercise name must be translated into the language of the user. In a previous blog post, we have discussed about a technique that can be used to have with Entity Framework multiple […]

  4. […] in the user language but the exercise name must be translated into the language of the user. In a previous blog post, we have discussed about a technique that can be used to have with Entity Framework multiple […]

  5. Patrick says:

    Hi, in an architecture where you have DAL/SERVICE/UI, where should be set the current language?

    I use DOMAIN/DTO/VIEW_MODEL and I initialy put the definition at the UI layer for the reason that it seems more logical that the language must be set in the front end because in a cenario where the UI can be something else, I can have a diferent way the get the CurrentUICulture.

    But now, I have some operations between entities in the service layer where I need to know the current language, so I would like to know if it’s a good idea to move the definition of the current languge in the service layer.

    Thanks

    • The current language should be set into the current principal thread. I usually set it into the UserProfile which is persisted. This way, when the user log in again, the system remember his preferences. The controllers (your base controller) should set the current principal thread to the UserProfile value.

      • Patrick says:

        And is it correct to make the decision:

        case “FR”:
        return French;
        case “EN”:
        return English;

        In the service layer based on the current language from the current thread?

        • Nothing is correct or wrong, it’s just a solution for a problem. You can do it differently if your need is different. The service layer doesn’t care about the language of the user. Check the edit that this post has. It doesn’t use the switch case. However, it does check the DisplayName to make it decisions.

  6. Andrey says:

    You will need to put hidden values in Editor template.
    Otherwise English will null French value and French value will null English value.

    @model Uco.Models.LocalizedString
    @Html.TextBoxFor(s => s.Current)
    @Html.HiddenFor(s => s.English)
    @Html.HiddenFor(s => s.French)

    • At the beginning, when I wrote this article, I though that we could with Entity Framework specify which property of the complex type to update. It’s not the case. Entity Framework doesn’t let you specify on a complex type the property to update, it’s considered a whole by itself. However, if you only want to display the current nothing stop you to simply load the entity back on the server side and update only the current one. This way, you do not need to expose to the view all languages (which can be more than 2 in a wider system). But, Andrey solution is totally correct.

  7. […] that is used by the Contest. The first one is a concept that I had already used and it concerns localized string. The LocalizedString is a class that has a property for each language supported. In this case, we […]

  8. […] InitialCapital_CurrencyTypeId. But, the problem is that Contest has 1 property called Name of type LocalizedString which has 2 properties called French and English and 1 property called InitialCapital of type Money […]

Leave a Reply

Your email address will not be published. Required fields are marked *