Press "Enter" to skip to content

ScottLilly.com Posts

Internationalizing an enum in C#, while storing a non-localized value to the database

One problem I’ve run into several times, and never found a good solution to, is storing an enum value in a database column. It’s kind of ugly to store a string for something that isn’t really a string.

In a recent project, I had an additional problem – I needed to internationalize this program. The value in the database needed to always be one language (Spanish, in this case). However, the value to display in the browser needed to be in the user’s current language.

Here’s how I handled it.

Preparing to create a localized enum

Choose your (data)base language

I decided to always store the value in the database in the application’s base language – in this case, we’ll use English, although it could be any language you want.

The base language should be the one that the programmers and DBAs understand, since that will make it easier to understand if they ever have to debug or change something.

The example

For this sample, the “enum” we’ll use will be to handle status. The three statuses will be “Open”, “OnHold”, and “Closed”.

Create the resource file(s)

For this example, I’m using a resource file named “Literals”, in a directory named “Resources”. Here are our three values to save in the database, with their corresponding display values:

Literals.resx

Literals.resx

And here’s the version for the Spanish text:

Literals.es.resx

Literals.es.resx

Remember to set the access modifier to Public for your resource files.

Extension method

To do the translation, I use an extension method.

InCurrentLanguage() takes any string and displays the value from the resource file for the current language, if it finds a match. If it doesn’t find a match, it will display whatever value was passed to it.

A nice thing about this method is that you can use it on any string in your application.

If you already have a class for your extension methods, you can add the InCurrentLanguage() method to it, instead of adding this new class.

ExtensionMethods.cs

using EnumI18N.Resources;
namespace EnumI18N
{
    internal static class ExtensionMethods
    {
        internal static string InCurrentLanguage(this string text)
        {
            return Literals.ResourceManager.GetString(text) ?? text;
        }
    }
}

Base LocalizedEnum class

Now we need the base class to use for the localized enum values.

This class accepts the value to store in the database as a parameter of the constructor.

When reading from, or writing to, the database, use the ValueForDB() method. To display the translated value, call either ToString() or ValueForUI().

LocalizableEnumValue.cs

namespace EnumI18N.Reference
{
    public abstract class LocalizableEnumValue
    {
        protected readonly string _databaseValue;
        protected LocalizableEnumValue(string databaseValue)
        {
            _databaseValue = databaseValue;
        }
        public override string ToString()
        {
            return _databaseValue.InCurrentLanguage();
        }
        public string ValueForDB
        {
            get { return _databaseValue; }
        }
        public string ValueForUI
        {
            get { return ToString(); }
        }
    }
}

Defining the localized enum

Now we can define our localized enum.

Here’s the code for ProjectState.cs.

The constructor takes the database value (passing it to the base class, and storing it there). There are also overrides for equality checks. You probably don’t need all of them, especially since you’ll only be instantiating the objects once, but I added them in case you do something different in your code.

ProjectState.cs

namespace EnumI18N.Reference
{
    public class ProjectState : LocalizableEnumValue
    {
        protected internal ProjectState(string databaseValue) : base(databaseValue)
        {
        }
        public override bool Equals(System.Object obj)
        {
            return Equals(obj as ProjectState);
        }
        public bool Equals(ProjectState projectState)
        {
            if(projectState == null)
            {
                return false;
            }
            return (ValueForDB == projectState.ValueForDB);
        }
        public override int GetHashCode()
        {
            return (_databaseValue != null ? _databaseValue.GetHashCode() : 0);
        }
        public static bool operator ==(ProjectState a, ProjectState b)
        {
            // If both are null, or both are same instance, return true.
            if(ReferenceEquals(a, b))
            {
                return true;
            }
            // If one is null, but not both, return false.
            if(((object)a == null) || ((object)b == null))
            {
                return false;
            }
            // Return true if the fields match:
            return a.ValueForDB == b.ValueForDB;
        }
        public static bool operator !=(ProjectState a, ProjectState b)
        {
            return !(a == b);
        }
    }
}

Creating the localized enum

Here’s where the localized enum gets created and populated.

Ignore the two constants for now. The rest of the code is what needs to be done to create a localized enum.

In the static constructor, the list of values gets populated. I’m using a constant for these, so I don’t have to worry about mistyping the value. Notice that the values of the constants exactly match the values used for the resource names in Literals.resx. This lets the InCurrentLanguage() method does its work.

There is also the private static class ProjectStates.

This class, along with the public static properties for each value, makes the localized enum appear like a normal enum when you use IntelliSense. ToList() lets you get the values for binding to controls (such as a combobox). And, finally, ForDBValue() returns the appropriate localized enum value when you pass it the value you’d store in the database.

LocalizedEnums.cs

using System.Collections.Generic;
using System.Linq;
using EnumI18N.Reference;
namespace EnumI18N
{
    public static class LocalizedEnums
    {
        public const string DISPLAY_MEMBER = "ValueForUI";
        public const string VALUE_MEMBER = "ValueForDB";
        private static readonly List<ProjectState> _projectStates = new List<ProjectState>();
        private const string PROJECT_STATE_OPEN = "Open";
        private const string PROJECT_STATE_ON_HOLD = "OnHold";
        private const string PROJECT_STATE_CLOSED = "Closed";
        static LocalizedEnums()
        {
            _projectStates.Add(new ProjectState(PROJECT_STATE_OPEN));
            _projectStates.Add(new ProjectState(PROJECT_STATE_ON_HOLD));
            _projectStates.Add(new ProjectState(PROJECT_STATE_CLOSED));
        }
        public static class ProjectStates
        {
            public static ProjectState Open { get { return _projectStates.Single(x => x.ValueForDB == PROJECT_STATE_OPEN); } }
            public static ProjectState OnHold { get { return _projectStates.Single(x => x.ValueForDB == PROJECT_STATE_ON_HOLD); } }
            public static ProjectState Closed { get { return _projectStates.Single(x => x.ValueForDB == PROJECT_STATE_CLOSED); } }
            public static List<ProjectState> ToList()
            {
                return _projectStates;
            }
            public static ProjectState ForDBValue(string valueFromDB)
            {
                return _projectStates.Single(x => x.ValueForDB == valueFromDB);
            }
        }
    }
}

Using the localized enum

Everything is in place, so let’s actually use this.

Here’s how I load the enum values into a combobox for a Windows Form.

The data source is the list, and I use the constants to bind the value and display members. Again, I like to use constants so I don’t lose type by accidentally mistyping a string value.

Form1.cs

using System;
using System.Windows.Forms;
namespace EnumI18N
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            cboProjectStatus.DataSource = LocalizedEnums.ProjectStates.ToList();
            cboProjectStatus.DisplayMember = LocalizedEnums.DISPLAY_MEMBER;
            cboProjectStatus.ValueMember = LocalizedEnums.VALUE_MEMBER;
        }
    }
}

You can also use the localized enum as the datatype for a property.

Here’s a class that has a ProjectState property, along with a constructor that you could use when populating it from the value you’d read from the database (where you would have previously stored the ValueForDB while inserting or updating the record).

Project.cs

using System;
using EnumI18N.Reference;
namespace EnumI18N.Models
{
    public class Project
    {
        public Guid ID { get; set; }
        public string Description { get; set; }
        public ProjectState State { get; set; }
        public Project(Guid id, string description, string state)
        {
            // Sample constructor to populate from database values.
            ID = id;
            Description = description;
            State = LocalizedEnums.ProjectStates.ForDBValue(state);
        }
    }
}

Summary

There is a bit of work to create these localizable enums. However, I’m a big believer in preparing your applications for internationalization. If you build something good, it’s a fairly simple way to expand your market.

2 Comments