DbResouceManager


This was a problem that needed to be solved to the okay of the decision makers. .NET 2.0 made a lot of improvements to how to localize/globalize ASPX pages. In the javascript/ASP application world where our current product lives translation was done via a method that queried the database passing in the English string as the lookup key and the user’s chosen language preference to return a translation of the English string. This translation was substituted in the appropriate place and auto-magically the English string was now a translated string. While this was crude but it was effective and the developer didn’t have to do anything special because enclosing the English string in the method call. I will admit that it was also pretty easy. No setting up strongly-typed names, no meta:resourcekey tags in the ASPX pages and no .resx files to manage.

The current product is translated into Spanish, Portuguese(Brazil), German and Italian. I have a lot of scrapping code that finds all of the strings and places them into an XML file. I have code that parses the XML files that are translated and places them into the database. There are small little apps that take the code out of the database and find strings that are missing translation.

On top of all that the translators we use are used to receiving the XML files and using their tools to translate. All in all it is a working process that would have to be changed in order to leverage the .NET way.

So I looked into doing some sort of scrapping of the controls on the page looking for English strings that needed to be translated. This would have to be recursive in order to find controls within controls. I had only a short amount of time to deliver the solution. Actually it was needed, like always, yesterday. So after two sleep deprived nights (not due to the pressure but due to my mind churning the different approaches) I saw two likely candidates to get this done. I could provide a BasePage that had the control looping and scrapping process that would be used on all ASPX pages. I really didn’t like this because it really added code to every page. What I did like was using a PageAdapter that would magically sit in the page life cycle process and do the control looping and scrapping. This way the code was in a single place and made maintaining a lot easier.

However there was this problem about using third party products for UI controls that was my real nightmare to control looping and scrapping. We use Telerik controls and are happy with them. But to recursively cycle through the controls and scrap the English strings was going to take me lot longer to code making delivery by ‘yesterday’ out of the question.

So I settled on a simple approach. I wrote a mimic static class that would be put in the code behind of the ASPX pages. Any properties that would have English string to be translated would call this static class with the method. The developer would have to jump to the code behind but it was similar to what they were doing before. So in the code behind they would do something like this:

SomeControl.Text = MyClass.Translate(”Some English String”);

Another cool thing I was able to add was to use String.Format in the class so:

SomeControl.Text = MyClass.Format(”Some English String {0} {1}”, arg1, arg2);

was possible. One of the issues that translators had was converting some of the sentences that had variables injected into them. So this gives them more flexibility.

One thing I did find out about some of the Telerik controls was that you had to translate the English strings by overriding the OnInit page event. You even had to translate the strings prior to calling base.OnInit(e) in order for the translated strings not to be replaced with the English strings during rendering. In most cases the translation could be done in Page_Load.

DbResourceManagerProviderFactory class

It all begins here by extending the ResourceProviderFactory by overriding CreateGlobalResourceProvider and CreateLocalResourceProvider. These two methods will return my DbResourceManager (a concrete implementation of IResourceProvider). All requests for strings go through this class.

All pages are parsed during design time and all of the explicit local and global resources are validated. DbResourceManagerProvider is used to complete this parsing. At runtime the page strings are localized for the resources.

If you have not added a <globalization… /> tag to the system.web section of the Web configuration file then the default call is to ResxResourceProviderFactory and uses the .resx files in the App_LocalResources directory.

BaseResourceProvider class

I created an abstract BaseResourceProvider class that implements IResourceProvider interface. This interface requires that you provide a GetObject(string, CultureInfo) and a ResourceReader property. This abstract class is used by the DbResourceProviderManager.

DbResourceManagerProvider class

This class uses the abstract BaseResourceProvider class and overrides the CreateResourceManager to return a ResourceManager type using the page name as the class key.

DbResourceReader class

The IResourceReader is obtained via the DbResourceManagerProvider. The main method is the GetEnumerator that returns a IDictionaryEnumerator object. This is where you code to go to the database and retrieve the key/value pairings for the strings. I place these in a Hashtable and return hashtable.GetEnumerator.

What about the connection string?

Where to get the connection string is always problematic as far as I’m concerned. I decided to have a property on the DbResourceManager that returns a connection string. I use another class I called ConnectionInfoBuilder that goes to whereever the connection string infornmation is stored and return a correctly formatted connection string. The connection string is encrypted in a file and the ConnectionInfoBuilder reads the file and decrypts the connection string. If I want to change this later I only have to modify the ConnectionInfoBuilder class. The DbResourceManager classes remain the same.

I’m trying to get an understanding of how all this works so I’m going to write them down here. I may get some of this wrong but I will correct it if I find inaccuracies later. ASP.NET 2.0 has made some major changes in how you deal with multiple languages for your web pages. The plain out of the box version is done automatically for you by generating the meta:resourcekey for the controls and creating a .resx file in the App_LocalResources directory (created for you too if its not there already).

If you want to keep the language strings in a database then you need to extend the functionality that is supported by the ASP.NET 2.0 platform. Whenever a web page is display there is an auto-generated source file for that page down in the WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\<web project name>\ directory on the drive that windows is installed on. I’m using VS2005, ASP.NET 2.0 and WindowsXP. Inside the generated web page file there are private methods for each of the controls on the web page. Some of the code in these private methods set properties on the control and this is where the code handles the strings for different languages. Here is some partial code from a private method:

private global::System.Web.UI.WebControls.Label @__BuildControlMyLabel() {

@__ctrl.Text = “My Text”;

@__ctrl.Text = System.Convert.ToString(this.GetLocalResourceObject(“MyLabelResource1.Text”), System.Globalization.CultureInfo.CurrentCulture);

}

You can see that this is a private method for a

<asp:Label Text=”My Text” meta:resourcekey=”MyLabelResource1”…/> tag in the web page.

The @__ctrl.Text = “My Text” is called first (default setting) and then later in the code it is overwritten and uses the GetLocalResourceObject method to get the string based on the resource key and the culture settings.

The default mechanism for getting the culture settings is to use the browser’s culture setting. The browser’s culture settings are found in the ‘Select Language’ dialog box. There are other ways to do this using config files or via code in the web page.

So for control’s with properties that usually take strings GetLocalResourceObject is the call that does it.

Extending the ASP.NET 2.0 Resource-Provider Model

The MSDN article can be found here.

As the number of .resx files grows you may reach a point where moving to the database becomes the way to manage all of the strings. Building a database resource manager is the way I’ll go.