C# Localize Properties by String Pattern with Resource File
Posted on: 2017-01-18
Imagine the scenario where you have several classes that inherit a base class and all theses children classes have an unique title, description and other string properties defined in a resource file. If you need the current language, the standard way to access the resource is fine. However, in the scenario where you need to have all defined localized string, you need to call the resource manager and explicitly use the string for the key as well as for the culture. Going in that direction is a step in redundancy where every child class needs to have a reference to the resource file manager, and repeat the property string that is defined in the base class for all languages. In this article, I'll show you a way to handle by convention, with a string pattern, a way to get the localized string for all desired language once (in the base class) instead of in all children.
At the end, we want something clean like the following code:
public class ChildA: Parent{ public ChildA(){} }
To do so, we need the parent to take care to fill the localized properties accordingly from the child's type.
public class Parent {
public LocalizedString Name { get; set; }
public LocalizedString Description { get; set; }
protected FeatureFlag() {
this.Name = ParentResourceManager.Instance.GetLocalizationNameByClassName(this);
this.Description = ParentResourceManager.Instance.GetLocalizationDescriptionByClassName(this);
}
}
You can skip the detail about the LocalizedString
type or if you are curious can go look at this article.
It's just a class that has a French and English string. The important piece if that the constructor invokes the ParentResourceManager
to retrieve the proper string. This resource manager looks like this:
public class ParentResourceManager : ClassResourceManager {
private static readonly Lazy<ParentResourceManager> lazy = new Lazy<ParentResourceManager>(() => new ParentResourceManager());
public static ParentResourceManager Instance {
get { return lazy.Value; }
}
private ParentResourceManager(): base(ApplicationTier.ParentResource.ResourceManager) { }
}
This class uses the generic ClassResourceManager
that define which resource file to look for the string and also be sure to have a single unique instance in the application. The generic class ClassResourceManager
handles the two methods used to retrieve the title and description. These methods could be simplified into a single one, but for the purpose of this article, let's keep it this way. The reason behind having this generic class is that you can reuse it for every type that has a different resource file. In short, it sets a pointer to the resource manager generated by the resource file. Indeed, the resource file needs to be set at auto-generated.
public class ClassResourceManager {
public static string ERROR_MESSAGE_FORMAT = "[{0}] not found for language [{1}] in file [{2}]";
public ClassResourceManager(ResourceManager resourceManager) { this.ResourceManager = resourceManager; }
public ResourceManager ResourceManager { get; set; }
public string GetLocalizationFor(string key, LanguageType language) {
string languageTwoLetters = "";
switch (language) {
case LanguageType.English:
languageTwoLetters = "en";
break;
case LanguageType.French:
languageTwoLetters = "fr";
break;
}
try {
var resourceString = this.ResourceManager.GetString(key, CultureInfo.CreateSpecificCulture(languageTwoLetters));
if (resourceString == null) {
return string.Format(ERROR_MESSAGE_FORMAT, key, language, this.GetResourceFileName());
}
return resourceString;
} catch (Exception) {
return string.Format(ERROR_MESSAGE_FORMAT, key, language, this.GetResourceFileName());
}
}
public LocalizedString GetLocalizationFor(string key) {
return new LocalizedString {
French = this.GetLocalizationFor(key, LanguageType.French),
English = this.GetLocalizationFor(key, LanguageType.English)
};
}
public LocalizedString GetLocalizationNameByClassName(object objectReference) {
var objectType = objectReference.GetType();
var name = objectType.Name + "_Name";
return new LocalizedString {
French = this.GetLocalizationFor(name, LanguageType.French),
English = this.GetLocalizationFor(name, LanguageType.English)
};
}
public LocalizedString GetLocalizationDescriptionByClassName(object objectReference) {
var objectType = objectReference.GetType();
var name = objectType.Name + "_Description";
return new LocalizedString {
French = this.GetLocalizationFor(name, LanguageType.French),
English = this.GetLocalizationFor(name, LanguageType.English)
};
}
private string GetResourceFileName() { return this.ResourceManager.BaseName; } }
The ClassResourceManager
has a pointer to the resource file and use the name of the child class to concatenate with the property string name. For example, with ClassA
, the developer must defined in the resource file ClassA_Title
and ClassA_Description
. If the developer forget, the code will thrown an exception telling exactly which resource name is missing which is convenient and pretty clear.
The whole idea is to stop having manual entries for localized string at the cost of depending on a pattern for properties that are shared across all children. Since we know which properties the parent needs to be localized, it's easy to have this one handle how to retrieve the localized string from a specific resource file.