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.
Remember Me