preload
Mar 06

This post will first explain the advantages you get from separating language and hard coded text from your application code and then show code examples in C# helping you getting started implementing language and text separating.

Lets have a look at the advantages:

Multi language support

When you separate the language from your application code it takes little effort to implement multi language support for your application. The only thing you need to do is to translate your original language file into the languages your application shall support and select on start up what language file to parse.

Maintainability

It is so much easier to maintain the language and texts in your application when these are found in a language file. You don’t have to search through your source code to fix misspelled words or bad sentences. It is also easy to change words and sentences. You also have the possibility to send the language file to a none technical person for QA, translation and so on.

Re-usability

In many applications you have what I call global words or phrases, by that I mean words or phrases that are similar and used throughout the application. A good example for this is button names (OK, Cancel, Back,…) and alert messages (Do you want to delete the item?). You can collect global values like this in one place in the language file and you don’t have to repeat yourself each time your need to display on of these global values. This is also a maintainability advantage. For example if you are changing the Cancel button name to Abort you have one place to update this instead of running a search and replace in your entire project.

Readability

The separating gives you a cleaner and easier to read code. You have now completely removed hard coded words and sentences from your logical code and the developer will not have to use any time or effort by reading this. You have also improved the readability of words and sentences in you application since they are all placed together in a file.

So it is quite easy to see and understand that separating language and hard coded text from your application code is a good practice.

Let go through some code examples to see how this can be achieved

I am using Visual Studio 2008, Windows Mobile 6 Professional SDK and .Net Compact Framework 3.5 for this example.

This is an example written in .Net Compact Framework but most of the code can be used directly by full .Net projects. And of course the principle can be used for all kind of projects and programming languages.

I am using three classes for parsing the language files. This code is developed as a Language module in my utility project (I extract code that most likely will be needed in future project into a utility project).

LanguageHandler.cs is the main class responsible for parsing language files and collect all language values in a hashtable:
LanguageHandler.cs

using System.Collections;
using System.IO;

namespace TechCon.Util.Language
{
    /// <summary>
    /// This class is responsible for handling language files.
    /// The class will hold a hashtable containing all language values.
    /// </summary>
    public class LanguageHandler
    {
        private Hashtable _languageContent;
        private readonly ILanguageParser _languageParser;
        
        public LanguageHandler(string languageFilePath,
                                        ILanguageParser languageParser)
        {
            _languageParser = languageParser;
            LoadLanguageFile(languageFilePath);
        }

        /// <summary>
        /// Check that file exists on languageFilePath and let
        ///  the ILanguageParser parse the file content to a hashtable
        /// </summary>
        /// <param name="languageFilePath"></param>
        private void LoadLanguageFile(string languageFilePath)
        {
            if (File.Exists(GetAbsoluteFilePath(languageFilePath)))
            {
                _languageContent = _languageParser.
                ParseLanguage(GetAbsoluteFilePath(languageFilePath));
            }
        }

        
        private static string GetAbsoluteFilePath(string relativeFilePath)
        {
            return System.IO.Path.GetDirectoryName(
                       System.Reflection.Assembly.GetCallingAssembly()
                       .GetModules()[0].FullyQualifiedName)
                   + "\\" + relativeFilePath;
        }

        public string GetText(string key)
        {
            if (string.IsNullOrEmpty(key))
            {
                return "NO_KEY";
            }
            else if (_languageContent == null)
            {
                return "NO_LANGUAGE_FILE_PRESENT";
            }
            else if (!_languageContent.ContainsKey(key))
            {
                return key;
            }
            else
            {
                return (string)_languageContent[key];
            }
        }
    }
}

I want the LanguageHandler to be independent when it comes to what kind of file format and structure the language files have. Therefore I have created a interface class called ILanguageParser.cs:
ILanguageParser.cs

using System.Collections;

namespace TechCon.Util.Language
{
    public interface ILanguageParser
    {
        Hashtable ParseLanguage(string languageFilePath);
    }
}

For this project I want to have the language file in XML format so I created LanguageXmlParser.cs that are implementing the ILanguageParser:
LanguageXmlParser.cs

using System.Collections;
using System.Xml;

namespace TechCon.Util.Language
{
    /// <summary>
    /// This class is a simple XML parser created
    /// for the following XML structure:
    /// <text name="the key" value="the value"/>
    /// The name attribute represents the key in a hashtable and
    /// the value attribute is the value mapped to the key.
    /// </summary>
    public class LanguageXmlParser : ILanguageParser
    {
        //Valid elements in the language xml file
        private const string ElementText = "text";
        //Valid attributes in the language xml file
        private const string AttributeName = "name";
        private const string AttributeValue = "value";

        private Hashtable _languageValues;

        public Hashtable ParseLanguage(string languageFilePath)
        {
            _languageValues = new Hashtable();
            XmlReader reader = new XmlTextReader(languageFilePath);
            ParseXml(reader);
            return _languageValues;
        }

        private void ParseXml(XmlReader reader)
        {
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        ParseXmlElement(reader);
                        break;
                    default:
                        break;
                }
            }
        }

        private void ParseXmlElement(XmlReader reader)
        {
            if (reader.Name.Equals(ElementText))
            {
                ParseTextElement(reader);
            }
        }

        private void ParseTextElement(XmlReader reader)
        {
            string tmpValue = null;
            string tmpKey = string.Empty;
            while (reader.MoveToNextAttribute())
            {
                if (reader.Name.Equals(AttributeName))
                {
                    tmpKey = reader.Value;
                }
                else if (reader.Name.Equals(AttributeValue))
                {
                    tmpValue = reader.Value;
                }
            }
            _languageValues.Add(tmpKey, tmpValue);
        }
    }
}

With the three simple classes shown above you have all you need to support language files in XML format and it is very easy to expand this to support other format like a plain text file because of the ILanguageParser implementation.

Lets have a look at how you can use the LanguageHandler concept described above in your application. I will use my pet project Run Smart With Me as an example for this.

First you need to create the XML language file, I chose to name these files by the language they support. Under you see my eng-us.xml file containing some global values and some values specific for the log in screen. It is important to use a good structure for your language files, this will increase the maintainability.
eng-us.xml

<?xml version="1.0" encoding="utf-8" ?>
<language name="eng-us" version="1.0">
  <!--Global button values-->
  <text name="button.ok" value="OK"/>
  <text name="button.cancel" value="Cancel"/>
  <text name="button.exit" value="Exit"/>
  <!--Login screen-->
  <text name="login.header" value="Please log in"/>
  <text name="login.username.label" value="User name"/>
  <text name="login.password.label" value="Password"/>
  <text name="login.rememberme.label" value="Remember me?"/>
</language>

I have created ApplicationData.cs as a static class that will hold the LanguageHandler (with the language values) and this class is reachable throughout the application:
ApplicationData.cs

using TechCon.Util.Language;

namespace RunSmartWithMe
{
    public static class ApplicationData
    {
        public static LanguageHandler Language { get; set; }
    }
}

The ApplicationData is populated from the Program.cs on start up:
Program.cs

using System;
using System.Windows.Forms;
using TechCon.Util.Language;

namespace RunSmartWithMe
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [MTAThread]
        static void Main()
        {
            // On start up load the eng-us.xml file
            // (parsed with the LanguageXmlParser) to 
            // the ApplicationData class
            ApplicationData.Language = new 
            LanguageHandler("eng-us.xml", new LanguageXmlParser());

            Application.Run(new LoginScreen());
        }
    }
}

OK, now I have the language values available and I will show you how to use these in your Form. Under you can see a code example from my LoginScreen.cs where I use the language value to apply text to the screen header:
LoginScreen.cs

using System.Windows.Forms;

namespace RunSmartWithMe
{
    public partial class LoginScreen : Form
    {
        public LoginScreen()
        {
            InitializeComponent();
            _lblHeader.Text = ApplicationData
            .Language.GetText("login.header");
        }
    }
}

This was a complete but yet simple example on how to separate language and hard coded text from your application code. It does not take to much effort or time to do achieve this, in fact I think I have used more time writing this post then implementing the language module in my utility project and using it in the Run Smart With Me application.

Happy coding :)

Follow me on twitter @PerOla

Share & enjoy
You can subscribe to my comments feed to keep track of new comments.

No Comments to “Separating language and hard coded text from your application code”

No Pingbacks to “Separating language and hard coded text from your application code”

Leave a Reply

Subscribe to my comments feed

Subscribe to my feeds Follow me on Twitter
DZone MVB