So I've been working on a test of the .NET globalization/localization techniques for ASP.NET (v1.1) and have been having relatively mixed results. The documentation and step-by-step examples have been a bit weak and the documentation for this area of the framework does not seem nearly as robust as other topics. This perception might be, however, just from my lack of true understanding of how this is all supposed to work and the associated terminology.
My task was to take a single html page, convert it to asp.net, and then allow it to automatically detect the user's preferred browser language “automagically” display the page (text, images, flash) in that user's langauge. For the initial test we decided to support English, German, and Portuguese. In each case we were supporting only the raw language rather than region specific contexts (i.e. [pt] rather than [pt-br] for General Portuguese rather than specifically Brazilian Portuguese).
The first thing I learned is that while the .NET framework has great support for globalizing / localizing winform apps, it is a bit lacking on (particuarly GUI-based) asp.net support. While digging into this topic, I learned that there were a handful of ways in which you could accomplish this - the two that I looked at most closely were string resource files and satelitte assemblies. Due to the relative simplicity of everything, I elected to use the string resource file method.
I figured I'd list pretty-much step-by-step what I did so that maybe the next person will have an eaisier time of this than I did:
- I needed to “get a handle“ on the user's preferred langauge. It made the most sense to do this in the global.asax file.
- To set the file up for this task, I added “using“ statements for System.Threading, System.Globalization, and System.Resources to the global.asax.cs file
- I modified the Application_Start method to create a text-file-based resource manager. The first parameter indicates the name or key that is the first part of the resource files (i.e. a resources file for the English language would have to be called global1app.en.resources). The second parameter indicates where on the disk the resources are located. I chose this location arbitrarily.
Application["RM"] = ResourceManager.CreateFileBasedResourceManager("global1app", Server.MapPath("./resources"), null);
- I modified the Session_Start method to determine the user's language preference and to store it
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(this.Request.UserLanguages);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(this.Request.UserLanguages);
- The next task was to prepare the page for globalization/localization
- The first step was to convert any and all text to asp.net label controls so that I could easily change their Text properties at runtime
- For the images, I changed them from html <image> tags to <asp:image/> tags wherever possible so that I could set the ImageUrl property at runtime.
- For the places where we embedded Flash objects in the page, I simply created private string variables in the class and change the html to use the value of the appropriate variable for the flash URL by using <%= VARNAME %> embedded in the “code“. This may not be the most elegant solution as it seems to be a bit of tangled code, but it certain worked well.
- In the code (besides the defaults), I only had to add a using statement for System.Resources
- In the Page_Load method I added code similar to the following:
if (this.Page.IsPostBack != true)
this.Label1.Text = ((ResourceManager)Application["RM"]).GetString("UI_label01");
this.Label2.Text = ((ResourceManager)Application["RM"]).GetString("UI_label02");
- I had a line for each of my labels, images, and embedded flash objects. This part seemed a bit tedious and “klugy“ to me... I think that I could probably write a helper class that would enumerate all of the controls on a page, loop through them, and, based on the control type look to the RM for a value (or set of values) to set for the given language... I'll have to look more into that later.
- The next task was to actually prepare the resources for the various langauges.
- The first task was to create string-resource files that contained a list of keys and values for the text to display on the web site as well as the URLs to the various language-specific images. There are a couple of ways to create string resource files. I struggled with this for quite some time, so let me explain...
- The first is to use a text editor such as notepad and create a list of name/value pairs one line at a time. This was initially most attractive to me because I could simply take a text file and send it to my translator and it would not be very confusing to them at all. The file would be in the following format making it very easy to work with:
; this is a comment line and localization for Spanish
UI_label01 = hola
UI_label02 = hasta luego
- The second way is to create a new assembly resource file within VS.NET and set the name value pairs there. This is nearly as easy for me (the developer) to interact with, but not quite as intuiative for the translators... especially if they do not have a good XML editor handy... and assuming they know what XML is. I eventually chose this option due to the fact that (I must have been doing something wrong) I could never get the language-specific accent characters to display on the resultant web site using the simple text file. I'm assuming that it had something to do with the character encoding of the original text file... maybe if I had opened notepad and created a unicode file from the beginning things would have worked as they were supposed to.
- In any case, I created a resorce file for each language and named it in a consistent format (this is important). Since my app was called global1app, I used the names global1app.en.resx, global1app.pt.resx and global1app.de.resx.
- I then used the resgen.exe tool (from the .NET framework SDK) and “compiled“ each of these files into .resources files (i.e. global1app.en.resources, etc.)
- Next I created a folder in the web root called “resources“ and copied my newly-created resources files to that location.
- Finally, I created unique images for each language (actually, someone else created them for me) and named them in the format of <image>.<culture>.gif. So, an image that had been logo.gif now had additional copies called logo.en.gif, logo.pt.gif, and logo.de.gif. These names are not crucial (the URLs are in the .resources file so it really could have been anything) but I chose this naming convention to keep things consistent.
- The last step is to test the asp.net page to see if everything works as it should. There are a number of different ways that you can accomplish this - the following steps are the easiest that I found and assume you are using IE 6.
- Open internet explorer
- Choose Internet Options… from the Tools menu
- Click the button named Languages…
- on the dialog that opens up, click Add
- From the list, select Portuguese (Brazil) [pt-br] and click OK
- Click Add again, and select German (Germany) [de]
- The language displayed on the web page is based on the order in the list, so you can move the language you want to see to the top using the move up and move down buttons
- click ok and then ok which should put you at the browser window.
Visit the web page above (or refresh the page if you are already there) and everything should change languages.
Finally... if everything worked properly, you should see the site in different languages based on the order of the languages you chose in IE.
I'm fairly pleased with how this all worked, although I'm still going to look into the notepad-style resource file as I know that this would be easier for my translation team. I also like the fact that if I want to add support for a new language, or update the existing support I can simply copy a new *.resources file to the web server - without recompiling the app or touching the code in any way - and everything “just works“. The final “cool“ thing is that if I was unable to procure some of the resources for a particular culture, I can simply omit them from the resources file for that language and when the framework doesn't find the requested resource string in the current culture's resource file, it will automatically look to the resource file for the default culture (configurable in the web.config file).