Source Code | DEMO
If you have a multi-language site, you have probably already worked with .resx files. Resx files are resource files that can contain strings, images, sounds... pretty much anything. However, in ASP.NET (at least in our typical scenarios), resource files mainly contain strings to be translated in multiple languages. Those needing a refresher course on ASP.NET localization should take a peek at this article: ASP.NET Localization. Let us take a typical ASP.NET application as an example. When you generate a resource file for a file named Default.aspx, VS.NET generates a new folder named App_LocalResources (if it doesn't exist) and it creates a new file named Default.aspx.resx in this folder.
This option will only be visible in the design view of an aspx or ascx file.
Default.aspx.resx will contain all strings that can be localized from Default.aspx. Default.aspx.resx is the default resource file for Default.aspx. It will contain the default language strings, in our case English. Should you need to offer the same application in more than one language, you will need to create locale-specific resource files with a similar filename. For example, Default.aspx.fr-CA.resx would be a resource file for Canadian French. The logic to retrieve a string from the appropriate resource file is built into the .NET framework (it depends on the current thread's culture).
Those who have built a dynamic web site supporting multiple languages know that managing resx files is a burden, especially when the application changes. TeddyMountain.com is a web site we recently created which provides English, French, and Danish versions. We use a CMS for most of the text, but some dynamic pages like the store locator contains text which needs to be in resources files. We collaborated with a resident of Denmark to translate the website; the translator is not a programmer and could not be trusted with the XML-based resx files. Furthermore, as the site is in constant evolution, we wanted a dynamic solution to avoid losing time exchanging Excel files.
Although there are some commercial applications out there, we decided to make a a simple tool to unify a set of resource files. We wanted to take advantage of the fact that the we were building a website and we could have the translator use a web-based tool to translate the website. This has the advantage of being able to see the changes immediately in-context, instead of simply translating text locally. We made a class that would merge our Default.aspx.resx, Default.aspx.fr-CA.resx, and Default.aspx.da.resx files into a single C# object that is easy to use. Once the data is in the C# object, the data can be modified and saved back to the disk later on.
We called this C# object ResXUnified. The only constructor to ResXUnified needs a path to a resx file. Then it will find all resx files related to it from different languages. Once you have constructed the object, you can access the data simply by using an indexor:
ResXUnified res = new ResXUnified("Default.aspx.resx");
string val = res["da"]["textbox1.Text"];
In the above code, we access the Danish language file and query the value of the "textbox1.Text" key. This information can be changed and saved back to the disk:
res[""]["textbox1.Text"] = "Home"; // Default language we can pass an empty string
res["da"]["textbox1.Text"] = "Hjem"; // Danish
res["fr-CA"]["textbox1.Text"] = "Acceuil"; // French
When we call Save(), only the files that were changed will be written to the disk. ResXUnified simply uses a Dictionary and a List to manage the keys and the languages. To save, it uses the ResXResourceWriter class provided by the framework, which makes it easy to manipulate resx files. Similarly, we read resx files using the ResXResourceReader class. Without these two classes, manipulating resx files would be much more complicated.
I won't include more code here since this is a pretty straightforward collection class.
Later, for the TeddyMountain.com website, we made a quick interface in ASP.NET (see the demo here) to display all the resx files in a project. We enable the user to add a language file and translate all the fields from the default language. Here is an example:
When a string needs to be translated, the textbox background color is not the same, this way it's easy for the translator to see what needs to be translated.
The generate local resource button in Visual Studio generates a lot of "useless" strings in resx files; we don't necessarily want to enter tooltips on every Label in our application. To make it easier to read, we designed an option to hide or show these empty strings.
This tool is pretty basic right now and there are more options that could be easily added. For example, we could add a list of files that remain to be translated or allow for multiline strings (notice we don't support line-breaks in our strings). We encourage you to modify the code and show off your enhancements!
Final notes: If you try the code out, please remember to give write access to the ASP.NET worker process in all the App_LocalResources folders (and the App_GlobalResources folder if you use it). Also, since changing resx files for the default language restarts the web application, it is recommended you use the tool on a development copy of your website.
Source Code | DEMO