Several people have asked questions about writing internationalized apps
with JSP.  Having just done so (only with Western European languages so
far), I have learned some things that may be interesting to others in
the same situation.  The basic components of my approach were as
follows:

(1) All internationalized strings are stored in resource bundles

I used property resource files named Resources.properties (the default
translation, which was English), and Resources_xx.properties (where "xx"
is the ISO code of the language), following the ResourceBundle naming
conventions.  I heavily used the parameter replacement features like {0}
to synthesize internationalized messages that contained elements that
originated in the database.

(2) Accessing the resource bundles from the web application

I created a JavaBean called  "BundleBean" that had a series of methods
with calling sequences like this:

    String message = bundleBean.getMessage(locale, "label.username");

and it looked in the correct resource bundle for the language of this
locale.  The bundle bean is stored in the application context in a
startup servlet, by calling:

    getServletContext().setAttribute("bundleBean", bundleBean);

to make it available to both servlets and JSP pages.

(3) Internationalize the business logic

We had to deal with things like the fact that many European cities have
different names in different languages, so our business object for a
city has two different property getters for the name:

    String getName() returns the name in the language of the default
Locale
    String getName(Locale locale) returns the name in the language of
the
        specified Locale, if it is different, or in the "canonical"
language.

(4) Store a Locale object for the user's preferrred language in the
user's session

When a user logs on, one of the things we know is their preferred
language (they can switch later, if they want).  To record that
knowledge, we store a java.util.Locale instance based on that language
in the session object, under key "localeBean".  If the user exercizes
the option to change languages, we just update the locale and the next
page display is auto-magically done in the newly chosen language.

(5) Separate business logic and presentation logic

I'm a big beliver in this separation, and it paid off in this
application.  All my form submits go to a processing servlet (in the
real app, it's actually an action procedure called by a single servlet)
that does the appropriate business logic, which is mostly independent of
language choice, then stores appropriate beans in the session or
request, and forwards to an appropriate JSP page to display the next
result, using RequestDispatcher.forward().

(6) A simplified internationalized login page using the above features,
then, looks something like this:

    <%@ page language="java" buffer="8kb" session="true" %>

    <jsp:useBean id="attemptBean" scope="request"
     class="com.mycompany.AttemptBean" />
    <%-- Contains username and password from previous attempt, if any
--%>

    <jsp:useBean id="bundleBean" scope="application"
     imports="java.util.Locale"
     class="com.mycompany.BundleBean" />

    <%
    Locale localeBean = (Locale) session.getValue("localeBean");
    if (localeBean == null) {
        localeBean = Locale.getDefault();
        session.putValue("localeBean", localeBean);
    }
    %>

    <head>
    <title>Logon Page</title>
    </head>
    <body>
    <form action="/logon">    // Logon processing servlet
    <table border=0>
        <tr>
            <th align=right>
              <%= bundleBean.getMessage(localeBean, "label.username") %>

            </th>
            <td>
              <input type="text" name="username"
               value="<%= attemptBean.getUsername() %>">
            </td>
        </tr>
        <tr>
            <th align=right>
              <%= bundleBean.getMessage(localeBean, "label.password") %>

            </th>
            <td>
              <input type="password" name="password"
               value="<%= attemptBean.getPassword() %>">
            </td>
        </tr>
        <tr>
            <td>&nbsp;</td>
            <td>
              <input type="submit" value="<%=
bundleBean.getMessage(localeBean, "button.LogOn") %>">
              <input type="reset" value="<%=
bundleBean.getMessage(localeBean, "button.Reset") %>">
            </td>
        </tr>
    </table>
    </form>
    </body>

NOTE:  In pages other than the login page, I use the presence or absence
of a Locale in the session as an indicator that the session has expired,
and forward control to the login page if it's missing.  The logic looks
like this:

    <%
    Locale localeBean = (Locale) session.getValue("localeBean");
    if (localeBean == null) {
    %>
        <jsp:forward page="/login.jsp" />
    <% } %>

(7) Processing servlets make decisions on where to go next

The servlet accessed by "/logon" does the following pseudocode (no
internationalization needed):
* Extract the username/password passed in the request
* Create an AttemptBean to store these values, and stash
  it in the request (used to redisplay the previous input if
  there is an error)
* Validate the user.  If it fails, stash a bean that causes an
  error message to be displayed (logic not shown in the JSP
  page above), and forward to "/logon.jsp" (the page above).
* If validation is successful, create a Locale object with this
  user's desired language, and stash it in the session.  Then,
  forward to "/welcome.go" (see below for what ".go" means).

(8) Dealing with pages that really need to be written separately

About 95% of the JSP pages in this app could be made multilingual with
only the need to look up message strings, as described above.  However,
the welcome screen that is displayed after login is an example where the
Marketing group really wants to write custom pages in each language.  We
established a naming convention similar to that for resource bundles,
creating pages named:

    /welcome_en.jsp (English)
    /welcome_fr.jsp (French)
    ... and so on ...

I could have made the caller (such as the login servlet above) calculate
the name of the page to go to every time, but this logic would be
duplicated everywhere, and need to be updated every time we added a new
language.

Instead, I created a clever (IMHO :-) little servlet called GoToServlet,
and mapped it to the ".go" filename extension.  It does this:
* Extracts the base name passed to this call.  If the login
  servlet forwards to "/welcome.go" as above, the base name
  (from getPathInfo(), minus the suffix) is "/welcome".
* Extracts the users's locale bean from the session, and gets
  the language suffix.
* To the base name, append an underscore, the language code,
  and ".jsp" to get the derived name.
* Forwards to that JSP page.

This gave me the ability to very quickly forward, or create a hyperlink
to, a page that was really presented in separate languages with no muss
or fuss on the caller's part.

None of these ideas are earth shattering, but the combination of
Java/Servlets/JSP makes a very powerful platform for building
internationalized apps.

Craig McClanahan

===========================================================================
To unsubscribe, send email to [EMAIL PROTECTED] and include in the body
of the message "signoff JSP-INTEREST".  For general help, send email to
[EMAIL PROTECTED] and include in the body of the message "help".

Reply via email to