Now and then you find yourself between a rock and a hard place in the attempt to make your code easy to understand, and easily maintainable at the same time.
I am currently working on a fairly comprehensive refactoring of a site, or more accurately, a series of similar sites. Amongst other things I have implemented the possibility of localizing these sites.
The localized text is placed in Resource.xml files, that are handled by a custom ResourceManager, and looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<Resource>
<!-- General terms -->
<item name="Wanted">Købes</item>
<item name="For_Sale">Sælges</item>
<item name="Merge">Fusion</item>
</Resource>
On the basepage i have a GetString method that uses the ResourceManager to get the localized text. I works nicely. No locked files, no sattelite assemblies and very transparent.
But one thing bugged me a little. All the strings were kind of magical, and hidden in the xml files - I like intellisense on everything for speed, accuracy and transparency. But on the other hand, I don't want to manually add all texts to a Strings class as well.
So i wrote a little simple code-generation. It is an inherited BuildProvider, which in a rather simple way reads your language file, and creates a Strings class with all the string references.
Heres the code. Its a first draft, so it might need a little work.
public class StringsBuildProvider : BuildProvider
{
public override void GenerateCode(AssemblyBuilder assemblyBuilder)
{
string fileName = base.VirtualPath;
CodeCompileUnit generated = GenerateUnit(fileName);
assemblyBuilder.AddCodeCompileUnit(this, generated);
}
private CodeCompileUnit GenerateUnit(string fileName)
{
CodeCompileUnit unit = new CodeCompileUnit();
CodeNamespace nspace = new CodeNamespace("Amino.Børs");
unit.Namespaces.Add(nspace);
CodeTypeDeclaration classDeclaration = CreateClass();
nspace.Types.Add(classDeclaration);
return unit;
}
private CodeTypeDeclaration CreateClass()
{
string file = ABContext.Current.MapPath("~/language/da-DK/resource.xml");
CodeTypeDeclaration cls = new CodeTypeDeclaration("Strings");
XmlDocument d = new XmlDocument();
d.Load(file);
foreach (XmlNode n in d.SelectSingleNode("Resource"))
{
if (n.NodeType != XmlNodeType.Comment)
{
cls.Members.Add(CreateField(n.Attributes["name"].Value));
}
}
return cls;
}
private CodeMemberField CreateField(string fieldName)
{
CodeMemberField field = new CodeMemberField(typeof(string),
string.Format("{0}", fieldName));
field.Attributes = MemberAttributes.Public | MemberAttributes.Static;
field.InitExpression = new CodeSnippetExpression(string.Format("\"{0}\"",
fieldName));
return field;
}
}
Then you just add it to the buildProviders section in the web.config. As i have several xml files i decided to create a custom extension as singular match for the buildprovider, and simply added an empty file called builder.stringsbuildprovider.
<buildProviders>
<add extension=".stringsbuildprovider
type="Amino.Bors.CodeGeneration.StringsBuildProvider, Amino.Bors"/>
</buildProviders>
So now i have all my resource reference strings at hand at all times without having to open the xml document.