It's All Greek to Me
August 9, 1999
Those of you who have read my column before may be aware that I'm an expatriate Englishman living in sunny Seattle—it really is sunny today, which is quite a rarity. One of the things that people from England and the United States are uniformly bad at is learning a foreign language. (This is a sweeping generalization, and I apologize in advance to those enlightened British and Americans who have taken the trouble to learn a second language. As a failed French and Latin scholar, I take my hat off to you.) That includes understanding the requirements of foreign countries.
Alas, some of this lack of forethought has percolated into Web development, and it's rare to see Web sites that deal with internationalization well. There are a number of explanations for this, ranging from English being the lingua franca of the Internet to the lack of support for internationalization in existing browsers, Web servers, and script languages. Fortunately, version 5.0 of the script engines introduced features to address this need. In this article, I'll cover how you can use these new features to your advantage.
As an interesting side note, the Windows Script Team is about as international as they come. We have 14 full-time staff consisting of 1 British, 2 Canadians, 1 South African, 1 Italian, 1 Australian, 1 Filipino, 1 Japanese, and 6 from the United States. If the script team doesn't get internationalization, there's no excuse!
As an example, I'm going to use a Web site that allows you to translate currencies from German to US formats and dates from British to US formats. I chose these because they illustrate some of the more commonly found problems and provide a simple example that can be easily understood (I hope). You can take a look at the page in action, either in .asp format or in an .htm version.
Once we have the locale, it's possible to change the behavior of the Web page with the new locale features using Visual Basic Script Edition (VBScript) version 5.0. There are two approaches: You can run the script code on the client or on the server. Running the code on the client is the most efficient use of network bandwidth and provides the cleanest user experience, but does rely on VBScript 5.0 being installed on all your users' machines. VBScript 5.0 is available for download either as a part of Windows Script 5.0 (allowing you to update Internet Explorer 3.x or Internet Explorer 4.0 machines) or as a part of Internet Explorer 5. But you might not want to rely on all your clients having VBScript 5.0 installed, so you can also use the new locale features within an Active Server Pages (ASP) file. While this means that users will have to refresh the page to get a result, it does make it considerably easier to take advantage of the new features, since upgrading the server once is easier than upgrading all clients.
The two new features added to VBScript version 5.0 that provide access to the locale settings are setLocale and getLocale. Both functions take one argument, either the ISO standard language abbreviation or the Windows LocaleID (LCID). I will use the ISO abbreviation in this article, since it corresponds to the value of HTTP_ACCEPT_LANGUAGE returned by ASP and is a little easier to decipher than LCID. When setLocale is called, it will set the locale of the script engine to the locale you supplied in the function call. Any conversion or formatting routines called, from that point on, will use the new locale.
The ability to set the locale of the script engine is particularly useful in Internet Explore, because Internet Explorer always sets the locale of the script engine to US English. This isn't some covert attempt at cultural imperialism from Redmond, but rather a mechanism to ensure that your Web pages will have a consistent behavior, regardless of the user's locale. This is useful, but when you want to give users the flexibility to enter data in familiar formats, you're stuck. Let's take a look at how my currency and date converter page gets around this potentially thorny problem. The HTML on the page is pretty simple: four text boxes for input and two buttons that have script code hooked up to their onclick events.
The date convert button, button1, runs the following code when it's clicked:
sub button1_onclick dim original ' set the locale to UK english original = setlocale("en-gb") mydate = CDate(UKDate.value) ' set the locale to US English original = setLocale("en-us") USDate.value = formatDateTime(mydate,vbShortDate) end sub
Once the variable original has been declared, its value is set to the return result of setLocale when called with the ISO abbreviation for Great Britain. The return result from setLocale is always the locale that the engine was set to before the setLocale was called. This can be useful in managing the transition between locales on a page. Once the setLocale function has been called successfully any conversion or formatting functions called in script will use the new locale. As a result, when the CDate function is called, it will know to expect a dd/mm/yy date format rather than the typical US mm/dd/yy format. Trust me, this difference in date formatting is annoying to any British or Canadians who've grown up expecting dd/mm/yy. Once the date has been converted, the program sets the locale back to the US English locale, currentLocale, and then calls the formatDateTime functon on the mydate variable. Since mydate is a date variant, which stores its value in a locale independent format, formatdatetime will call out to OLE automation and apply the formatting that is assigned to the US English locale.
The currency converter button runs similar code, except that it sets the locale to be German, converts the value from the text box, multiplies it by the current exchange rate, and then converts that into a currency variant. Once the conversion is complete, it sets the locale back to be UK English and formats the currency using the UK English formatting rules:
sub button2_onclick dim original original = setlocale("de") myvalue = CCur(GermanNumber.value * 2.89) original = setLocale("en-gb") USNumber.value = formatCurrency(myvalue) end sub
Try the page out. Enter dates in dd/mm/yy format, and the sample changes them to mm/dd/yy format. Enter numbers in German format, and it converts the value to British pounds. German number format is different from English in that it uses a comma as the decimal point and a period (full stop to those British readers out there) as the thousand separator, so 5.000,99 is equivalent to 5,000.99.
The same techniques used on the client can be applied to the server, but it is important to determine from where users are coming. This can be the biggest challenge, and while there are no foolproof mechanisms (other than asking them directly), you can get a pretty good indicator from browser information that is passed to your Web server in the HTTP_ACCEPT_LANGUAGE header variable. This will return the language setting for the browser being run on the user's machine, and is the key to designing a Web site that can change its behavior depending on the country of the user. This approach has its problems, because you can't guarantee that users have their machines set up correctly. You might want to use the HTTP_ACCEPT_LANGUAGE header variable as an indicator of their whereabouts and ask them whether it's correct.
Once you've ascertained where the request originated, you can change the behavior of your program accordingly. If you were really smart, you could have different content stored for different languages so that your Web page would be fully translated for your users. To make my life a little simpler, I'm not going to do that, but you could change the example I provide to your language of choice.
In the example .asp code, the HTTP_ACCEPT_LANGUAGE header variable is assigned to a variable named locale:
Dim locale setlocale = request.servervariables("HTTP_ACCEPT_LANGUAGE")
Tip It's more efficient to store the value of HTTP_ACCEPT_LANGUAGE in a script variable than to keep re-querying the request object every time we need to get at its value.
To illustrate the use of server code, I've taken the client page and migrated the code to run on the server. Instead of running client-side script when a user clicks a button, the onclick event generates a request to some server-side code, which does the conversion and then returns the result. It means the page refreshes, but allows you to write an internationalized Web page that runs in pretty much any browser on any platform. Remember, on the Internet, nobody knows you're running Visual Basic!
The code for the server script is similar to the client example, with a few subtle changes to take into account the server environment.
dim intResult dim userLocale dim USDate,UKDate,GermanNumber,UKNumber ' Get the locale of the user userLocale = request.servervariables("HTTP_ACCEPT_LANGUAGE") ' we've posted to ourselves so update if request.servervariables("REQUEST_METHOD") = "POST" then intResult = convert() end if function convert() ' Turn on error trapping so that when ' a netscape browser returns en rather than a fully on error resume next ' Set the locale to the browsers locale original = setLocale(userLocale) ' set UKDate to be the value of the form field from the client UKDate = request.form("UK_Date") ' Convert the string to a date mydate = CDate(UKDate) ' Change the locale to be US original = setLocale("en-us") ' Call formatdatetime to format the date in US long date format ' style to make sure we don't have any Y2K issues USDate = formatDateTime(mydate,vbLongDate) ' Change the locale to German original = setlocale("de") ' Get the German formatted number GermanNumber = request.form("German_number") ' Convert the value multiplied by the exchange rate to a currency myvalue = CCur(GermanNumber * 2.89) ' Change the locale to British original = setLocale("en-gb") ' Format the currency using the British settings UKNumber = formatCurrency(myvalue) end function
The key difference between the client and server code is that the locale of the user is being retrieved from the HTTP request variables sent by the browser. An interesting, and somewhat infuriating, note: Internet Explorer and Netscape Navigator return different locale settings! Quel surprise, you say. In response, I changed my code to check whether the setLocale works if it doesn't keep the locale setting to be the server's locale. It's not pretty, but at least the page doesn't throw an error. Navigator returns only en as the language, which is fine but still doesn't provide you with any region information. While you know your users want English, you don't know whether they are from the US, Great Britain, or somewhere else in the English-speaking world. You could easily modify the response to ask the user for their region and then use that to set the locale of the server.
All of the code above has been in VBScript 5.0, which doesn't help you if you're a JScript developer. So, why no support in JScript? The main reasons are standards conformance and compatibility. Microsoft could go ahead and add support in JScript, but the usefulness of this is greatly reduced if it's not in the ECMAScript specification and supported by Netscape. Rather than unilaterally add locale support to JScript, we are working with all the members of the ECMAScript committee to ensure that localization support is added to a future specification. This way, hopefully, Netscape and Microsoft will implement the feature identically, and you won't have to deal with those infuriating inconsistencies. As a result, the next big release of JScript is planned to have localization support—and, before you ask, we haven't set a date for when that will be available, but it won't be for a while yet.
Dealing with the many differing locale specifics is a major challenge when developing Web applications that will be used in multiple countries. It's becoming increasingly important, with the release of technologies such as Windows 2000, to be able to support these scenarios. The new locale features in VBScript are a small step toward making that easier, and we're working hard on the next release of Windows Script to make it even easier. As ever, we welcome your feedback. Feel free to e-mail firstname.lastname@example.org or post to our newsgroups.