Document last updated 18 April 2001. We are very interested in feedback that would make these materials better. Feel free to write the author, Jonathan Revusky.
This directory contains a minimal example of internationalization using Niggle. As was pointed out in the "Hello, Niggle" example, Niggle leverages the open source Freemarker library for its template capability. Though Freemarker is a capable template engine, it was not written with any special dispositions for dealing with the issues involved in web site localization -- e.g. maintaining different versions of a page template for different audiences. Niggle remedies this by providing a higher-level API that wraps the Freemarker and also provides for localization via the canonical java naming scheme.
As usual, we start with the mechanical steps that allow you to get the application running and then we explain the various parts.
We assume that, at the very least, you have successfully built and run the "Hello, Niggle" example.
I'll get straight to the point. Here are the steps involved in building and running this example:
You have probably already noticed that there are 2 new elements in this example. The first is that we used a web.xml configuration file to set up a mapping between the *.nhtml extension and the I18N servlet. This was not absolutely necessary to demonstrate the functionality, but it does show that things can be set up in such a way as to make this a fairly elegant, transparent solution. The second novel element is that, rather than just having a single welcome.nhtml file, we have localized versions for English, Spanish and French, that follow the canonical Java localization naming scheme. We have also externalized certain strings for those languages in separate .properties files.
The key thing to understand is that the getPage()
routine that
vends a page template is locale-aware. Every
ServletInteraction
instance has a Locale associated with it.
The template vending mechanism takes this into account, so that if the
locale is "es" (Spanish language, no country specified) or "en_GB"
(English language, Great Britain) or whatever, the system will try to find
the resource that best matches the locale. In the latter case of "en_GB",
let's say you invoke:
page = getPage("welcome.nhtml");
Then the system will look for the resource "welcome_en_GB.nhtml" and failing that, will try "welcome_en.nhtml", and if that resource is not found, will go to the most generally specified template: "welcome.nhtml". This pretty much mirrors the canonical Java internationalization scheme.
Now, let's take a look at the java code in this example. The
deduceLocale()
method is a hook that can be overridden
by the application programmer to provide the logic by which the
application decides what the preferred locale is. If you do not override
this method, the locale will simply be taken to be whatever
the base servlet API's request.getLocale() method returns.
In order to demonstrate internationalization, our implementation of
deduceLocale()
simply sets the locale as being France.
(Of course, you can play around and experiment with this by changing that line.)
Now, obviously, a real-world application would not be so simple-minded.
It would set the locale based on user-preferences, perhaps remembered via a
cookie. Or it might take into account what the referring site was. If the
referring site was yahoo.fr, it would make sense to set a French language
locale, whereas if it was lycos.de, it might make more sense to set
a German language locale. It might also make sense to try reverse DNS
lookup to try to deduce a user's locale. Of course, the user should be
given a chance at later points to explicitly change her preferred language.
Note that the servlet 2.2 API introduced the request.getLocale() call. This method gives you the locale based on the Accept-Language HTTP header. Note that the client-side user can adjust what is in this header in either Internet Explorer or Netscape via user preferences. I would venture to say that few users ever adjust this, so that what is most likely is that someone using the Spanish-language version of Netscape, say, has "es-ES" as their Accept-Language header.
In passing, I would discourage you from relying solely on this mechanism. On the one hand, in the absence of other indications, it is a perfectly good starting pint, since a Spanish speaker will likely be using the Spanish version of a browser. However, many Spanish speakers may be using the English version of Netscape or IE and might still prefer to see localized Spanish-language content, if it is available. This is a non-trivial issue that requires some thought and that is why I preferred to simply provide a deduceLocale() hook for the application programmer to override. You can decide what heuristics you want to use to decide what the preferred locale is.
In execDefault()
we assume that the user has invoked the
servlet via the *.nhtml mapping defined in web.xml. If the .nhtml template
specified in the URL does not exist, it uses "error.html". Note
that the error page is not localized, though it could perfectly well be.
We could perfectly well have an error_fr.nhtml and an error_es.html for
example.
When you went through the example and opened the URL: http://localhost:8080/niggletut/welcome.nhtml you saw the message: Je vous souhaite la bienvenue en la langue de Descartes. So far so good, since the strings.author was localized to Descartes of "I think, therefore I am" fame.
Now, some of your more resourceful users may come to realize that they can override the locale by appending the _xx to the page filename. Try opening: http://localhost:8080/niggletut/welcome_en.nhtml and have a look. You see what I see?
I welcome you in the language of Descartes.Hey, that's not true! Shouldn't it be the language of Shakespeare? You know, the guy who wrote the plays?! Well, that's a tough one. You see, by specifying the page template directly, the user got the English language template, but the locale was still set to France, so the externalized strings that got exposed were the ones in strings_fr.properties.
Well, I'll confess that I'm not sure what to do about this. Nobody said localization was a simple problem! One solution would be to be more sophisticated in deduceLocale() and account for this possibility.
Another issue you might have with this solution is that if you change any of the templates, you will need to restart the server for the changes to be reflected. The reason for this is that, by default, the system is finding the templates via the servlet's classloading mechanism. That's why we put the page templates in the same place that we put the .class files. There is another way to do this that allows us to change the templates while the servlet is running. You can specify a RESOURCE_BASE init-param for the servlet and that way, the system will assume that the page templates are located relative to that path. This is useful in particular when your site is under constant development. You certainly do not want to have to restart the server to see a small change reflected on a page. On the other hand, using the servlet classloader allows you to put everything, java classes and page templates in one .war archive and have everything be resolved relative to that. And that is a very attractive deployment option.