Open-Source I18N Tools
Posted on Mar 29, 2006
If working on globalizing Hasbro's web sites has taught me one thing, it is that the internationalization process can be very difficult and painful (so much so for me that I was hesitant even to write about it :| ). You also quickly learn that, while ColdFusion's support for internationalization (i18n) has taken dramatic leaps in both 6.1 and 7, there are a number of functions that you may require that ColdFusion isn't equipped to handle out-of-the-box. Thankfully, Paul Hastings (through his company Sustainable GIS) offer a wealth of i18n tools, free and open-source (these include, all but 1 of the open-source i18n projects on the ColdFusion Open-Source List at the moment). While I am certainly no expert in i18n, I wanted to give a beginner's experience using the Geolocator CFC and Resource Bundles CFC in a recent project.Geolocator CFC
The purpose of the geoLocator CFC is to try to determine a user's geographic location utilizing the user's IP address and the browser language. In ColdFusion you would generally pass these as CGI.REMOTE_ADDR and CGI.HTTP_ACCEPT_LANGUAGE respectively. The download includes 3 versions for you to use. The first, uses the built in Java locales in ColdFusion, and requires that you install the inetAddressLocator.jar via the ColdFusion administrator. A second version uses a technique laid out by Spike Mulligan for loading a Java class from a relative file path. The third version makes use of IBM's ICU4J Project for those whom the core Java locales are not enough. In my test, I chose to use the remote classpath version.
In my case, I wanted to set a cookie for a default locale for a user using geoLocation. It is important to note that you should always provide a way to override locale settings that you establish via geoLocation (which I did). I placed my code in the onSessionStart() function of my application.cfc, and it looked like this:
<cftry> <cfset geoLocator = createObject("component","com.geoLocation.remoteClasspath_geoLocator") /> <cfset myLocale = geoLocator.findLocale(CGI.REMOTE_ADDR,CGI.HTTP_ACCEPT_LANGUAGE) /> <cfif structKeyExists(application.settings.locales,myLocale)> <cfcookie name="userLocale" value="#myLocale#" expires="never" /> <cfelse> <cfthrow type="locale.notSupported" /> </cfif> <cfcatch type="any"> <cfcookie name="userLocale" value="#application.settings.defaultLocale#" expires="never" /> </cfcatch> </cftry>
As you can see, this is pretty easy (and even some of this is probably only relevant to my project). Basically, I load in the geoLocator cfc and run the findLocale() function. The if is checking to see whether the locale is one of the locales I am supporting for the given site, if not I throw an error, which I catch. If either the geoLocator fails or an error is thrown because this site doesn't support the user's determined locale, I set the cookie to whatever locale is the site's default locale (in most of my cases, this will be en_US). Keep in mind, take all of this with a grain of salt, as I said, I am not an i18n expert (and Paul, feel free to correct me), but in my tests, this bit of code has worked well for me (and I have been able to test its relative accuracy by passing in ip addresses manually).
(on a side note, we started running Country Hawk around the same time for other reasons, and in my opinion Paul's CFC was far easier to get running and worked as well or better - oh, and it's free)
Resource Bundles CFC
While many people may be familiar with the variation of the Resource Bundle CFC that is included in BlogCFc, that version places the resource bundle in a ColdFusion structure. The current version uses Java to parse a "pure Java resource bundle". I created my resource bundle via updated both CFCs recently, and I have not tested the updated versions (in which some new features have been added). In fact the version I downloaded had only two versions at the time (though this was relatively recent).
In my experience, the resource bundle CFC was very easy to implement. I did make some adjustments to the version that I downloaded however to allow me to keep the CFC in the application scope. For instance, I added an init() method that returned an instance of the component, and I created getters and setters for the file, locale and debug values. This allowed me to load the component once into the application scope, but easily switch and reload locales without having to reload the component and pass the values again. Making these changes should be straightforward for anyone wishing to do the same thing.
Once that is done, I simply load the resource bundle CFC like this:
<cfset application.rb = createObject("component","com.rb.javaRB").init(expandPath("rb/playskool.properties"),application.settings.defaultLocale) />
The first argument is my base properties file and the second is the locale, in this case my applications default locale. I would load different locales like this in my onRequest method of application.cfc:
<cfset setLocale(session.user.locale) /> <!--- if the locale of the resource bundle doesn't match the request locale ---> <cfif getLocale() NEQ application.rb.getRbLocale()> <cfset application.rb.setRbLocale(session.user.locale) /> <cfset application.rb.loadResourceBundle() /> </cfif>
The first line is setting the locale for the request (this is based on the cookie I set above, but for the life of me I cannot remember why I placed it in the session scope...). The second checks the current requests locale against the resource bundle components locale; if they are different, a new locale is set and the proper resource bundle is loaded. The reasons for handling this in this manner may not be relevant to your application (and I have no idea how this relates to best practices), but it worked in my test application (which, it may be noted, isn't live yet anyway).
Well, hopefully I haven't offended anybody's better sensibilities with the techniques I use (or maybe by chance they are good practices ;-), but the point of this post was really to give some promotion to the work Paul Hastings (and SustainableGIS) is doing by providing these tools. More than likely, the way the web is headed, if you haven't needed these tools yet, you will in the near future; and when you do, they will save you a lot of headaches.
Comments
Have you got any ideas of why I get an error relating to <cfset variables.geoLocator = loader.loadClass("net.wetters.InetAddressLocator").newInstance()> in remoteClasspath_geoLocator. I cant find help anywhere!
Regards
Posted By Dave / Posted on 05/09/2006 at 1:53 PM
Yes (or at least I think so). If my memory serves me correctly, there was an update to the InetAddressLocator.jar that was not reflected in some versions of the component. Have you downloaded the most recent version from the sustainableGIS web site? I may be able to send you my copy of the CFC and the jar if you can't locate a copy that works.
Posted By Brian Rinaldi / Posted on 05/09/2006 at 3:54 PM
Yes, its the latest. Any help you can provide would be appreciated.
Posted By Dave / Posted on 05/09/2006 at 4:43 PM
Just a small warning about putting setLocale in the Application.cfc:onRequest - it breaks <cfcache> for some obscure reason, claiming that [date time] is not a valid date.
Posted By S / Posted on 05/14/2008 at 5:26 PM
If I understand everything it seems that you change the javaRB.cfc and added an init that alow you to have better instanciation and caching capabilities. I think I want to do the same things. I am also interrested to see your script for "getters and setters for the file, locale and debug values".
Could you share your code concerning the javaRB additions ?
Thanks
Posted By eric00000007 / Posted on 07/21/2008 at 7:08 AM