The NetBeans E-commerce Tutorial - Adding Language SupportTutorial Contents
The goal of this tutorial unit is to demonstrate how to enable language support
for a web application. "Language support" here refers to the ability
to display page views according to the customer-specified languages. Within the
context of the In order to accomplish this, you rely on Java's support for internationalization. You create a resource bundle for each language and let the Java runtime environment determine the appropriate language for incoming client requests. You also implement a 'language toggle' to enable users to switch the languages manually. The NetBeans IDE provides special support for localizing application content. This includes a Customizer dialog that enables you to add new locales to an existing resource bundle base name, as well as a special Properties editor that lets you view and edit key-value pairs for all locales in a table layout. These are both utilized in this tutorial. You can view a live demo of the application that you build in this tutorial: NetBeans E-commerce Tutorial Demo Application.
Notes:
Understanding Resource BundlesIn Java, a resource bundle is a representation of the
Resource bundles contain locale-specific objects. When your program needs a locale-specific resource, a String for example, your program can load it from the resource bundle that is appropriate for the current user's locale. In this way, you can write program code that is largely independent of the user's locale isolating most, if not all, of the locale-specific information in resource bundles. From the Javadoc, you can also note that the messages_en.properties meats=meats bakery=bakery messages_cs.properties meats=maso bakery=pečivo In the above example, ' Making Pages MultilingualReturning to the
In order to implement the above points, divide the task into two parts. Start by creating basic bilingual support for page views. Once bilingual support is in place, implement the language toggle that enables users to manually switch languages. There are three basic steps that you need to follow to incorporate multilingual support into your web pages.
The following exercise demonstrates how to integrate English and Czech language support
into the
Create Resource Bundles
We now have the following resource bundles defined:
You might assume that if the default bundle is in English, then there is no need to create a resource bundle explicitly for English. However, consider the following scenario: a client browser's list of preferred languages includes both Czech and English, with English taking precedence over Czech. If the application doesn't provide a resource bundle for English but does for Czech, pages sent to that browser will be in Czech (since a Czech bundle was defined). This is clearly not the desired behavior for that browser. Register the Resource Bundle with the ApplicationThe purpose of this step is to inform JSTL's format (i.e.,
The topic of setting context parameters is also covered in Connecting the Application to the Database.
Replace Hard-Coded Text with
|
Use-case | Outcome |
---|---|
1. Browser has no preferred language | English displays |
2. Browser prefers only English | English displays |
3. Browser prefers only Czech | Czech displays |
4. Browser prefers only Korean | English displays |
5. Browser prefers Korean and English; Korean takes precedence | English displays |
6. Browser prefers Korean and English; English takes precedence | English displays |
7. Browser prefers Korean and Czech; Korean takes precedence | Czech displays |
8. Browser prefers Korean and Czech; Czech takes precedence | Czech displays |
9. Browser prefers English and Czech; English takes precedence | English displays |
10. Browser prefers English and Czech; Czech takes precedence | Czech displays |
11. Browser prefers, in the following order, English, Czech, Korean | English displays |
12. Browser prefers, in the following order, English, Korean, Czech | English displays |
13. Browser prefers, in the following order, Czech, English, Korean | Czech displays |
14. Browser prefers, in the following order, Czech, Korean, English | Czech displays |
15. Browser prefers, in the following order, Korean, English, Czech | English displays |
16. Browser prefers, in the following order, Korean, Czech, English | Czech displays |
Rather than stepping through all 16 scenarios, we'll demonstrate how to examine scenario 3 above, in which the browser's preferred language is Czech, using the Firefox browser.
Czech [cs]
. Then
click the Add button. The Czech language is added to the text area.
Now that basic Czech-English language support is in place, continue by implementing the language toggle in the application's page views. We can divide this task into three parts:
header
JSP fragment in the editor. Press
Alt-Shift-O (Ctrl-Shift-O on Mac), then type 'h
' in the dialog and click OK.
header.jspf
file, locate the first <div class="headerWidget">
tag (line 56), and replace the [ language toggle ]
placeholder text with the
following HTML markup.
<div class="headerWidget"> <%-- language selection widget --%> english | <div class="bubble"><a href="chooseLanguage?language=cs">česky</a></div> </div>This markup implements the language toggle's appearance when English is the displayed language. In other words, the toggle provides a link allowing the user to select the Czech (i.e., '
česky
') option. The link is used to send a request for
chooseLanguage
, and creates a query string (?language=cs
) that
specifies the requested language code.
Note: Recall that in Unit 5, Preparing
the Page Views and Controller Servlet, you set the ControllerServlet
to handle the /chooseLanguage
URL pattern.
Snapshot 8 includes the jQuery
JavaScript library and takes advantage of various UI effects to enhance the appearance
and behavior of the website. Aside from a
jQuery plugin
for client-side validation (discussed in the previous
tutorial unit), the snapshot implements an easing effect for category headings in
the welcome page, as well as for category buttons in the category page. Configuration
is included in header.jspf
of the project snapshot. Rounded corners are
implemented using CSS3's border-radius property (applied in affablebean.css
).
<div class="headerWidget"> <%-- language selection widget --%> <c:choose> <c:when test="${pageContext.request.locale.language ne 'cs'}"> english </c:when> <c:otherwise> <c:url var="url" value="chooseLanguage"> <c:param name="language" value="en"/> </c:url> <div class="bubble"><a href="${url}">english</a></div> </c:otherwise> </c:choose> | <c:choose> <c:when test="${pageContext.request.locale.language eq 'cs'}"> česky </c:when> <c:otherwise> <c:url var="url" value="chooseLanguage"> <c:param name="language" value="cs"/> </c:url> <div class="bubble"><a href="${url}">česky</a></div> </c:otherwise> </c:choose> </div>In the above implementation, you rely on conditional tags from JSTL's
core
tag library to display the left and right portions of the toggle according to the language
used by the request locale. What is the "language used by the request locale"?
When a request is made, the browser passes a list of preferred locales in the Accept-Language
HTTP header. The Java runtime environment on the server reads the list and determines
the best match based on the locales defined by the application's resource bundles. This
match is then recorded in the ServletRequest
object, and can be accessed
using the getLocale
method. For example, you could access the preferred
locale from a servlet with the following statement.
request.getLocale();
You can use the IDE's HTTP Monitor (Window > Debugging > HTTP Server Monitor) to examine HTTP headers for client requests. In order to use the HTTP Monitor, you need to first activate it for the server you are using. Unit 8, Managing Sessions provides a demonstration under the sub-section, Examining Client-Server Communication with the HTTP Monitor.
To determine the language of the preferred locale, you use the Locale
class' getLanguage
method. Again, from a servlet you could access the
language of the client request's preferred locale with the following.
request.getLocale().getLanguage();
Returning to the code you just added to the header.jspf
fragment,
you utilize the pageContext.request
implicit object to access the
ServletRequest
for the given client request. Using dot notation, you
then proceed to call the same methods as you would from a servlet. In the above
example, accessing the "language used by the request locale" is as simple
as:
${pageContext.request.locale.language}
Note: The above implementation uses <c:url>
tags to set up the toggle link. This is done in order to properly encode the request
URL in the event that URL rewriting is used as a means for session tracking. Unit 8,
Managing Sessions provides a brief
explanation of how the <c:url>
tags can be used.
header.jspf
file. This will enable us to
check whether the toggle is properly rendering according to the client request's preferred
language. Enter the following after the page's <body>
tag.
<body> <%-- Language test --%> <p style="text-align: left;"><strong>tests:</strong> <br> <code>\${pageContext.request.locale.language}</code>: ${pageContext.request.locale.language} </p> <div id="main">
cs
'
is the preferred language.Now that the toggle is in place and it appears according to the language displayed
in the page, let's continue by adding code to the ControllerServlet
that handles the request sent when a user clicks the link in the language toggle.
As indicated in the current language toggle implementation from step 4 above, the requested URL with query string looks as follows:
chooseLanguage?language=en
chooseLanguage?language=cs
Our goal is to register the language choice, and then display both the page view and
language toggle based on the chosen language. We can accomplish this by extracting
the language
parameter from the query string and creating a session-scoped
language
attribute that remembers the language selected by the user. Then
we'll return to the header.jspf
fragment and apply the
<fmt:setLocale>
tag to set the page language based on the user's choice. With the <fmt:setLocale>
tag we can manually switch the language used in the page display. We'll also modify
the language toggle so that if the language
attribute has been set, the
toggle's appearance is determined according to the language
attribute's
value.
ControllerServlet
in the editor. Use the Go To File dialog
- press Alt-Shift-O (Ctrl-Shift-O on Mac), then type 'controller
' and
click OK. In the opened file, locate the portion of the doGet
method
that handles the chooseLanguage
request (line 126).// TODO: Implement language request
comment and enter code
to extract the language
parameter from the request query string.
// if user switches language } else if (userPath.equals("/chooseLanguage")) { // get language choice String language = request.getParameter("language"); }
language
parameter in the request scope. Add the following.
// if user switches language } else if (userPath.equals("/chooseLanguage")) { // get language choice String language = request.getParameter("language"); // place in request scope request.setAttribute("language", language); }
index.jsp
welcome page when the language toggle link is clicked.
Add the following code.
// if user switches language } else if (userPath.equals("/chooseLanguage")) { // get language choice String language = request.getParameter("language"); // place in request scope request.setAttribute("language", language); // forward request to welcome page try { request.getRequestDispatcher("/index.jsp").forward(request, response); } catch (Exception ex) { ex.printStackTrace(); } return; }Naturally, forwarding the user to the welcome page regardless of what page he or she is on is not an ideal way to handle the language toggle's behavior. We'll return to this matter in the next sub-section, Enable the Application to Keep Track of the Originating Page View. For the meantime however, this will allow us to examine the results of the current language toggle implementation when running the project.
header.jspf
fragment (If the file is already opened
in the editor, press Ctrl-Tab and choose the file.) and apply the
<fmt:setLocale>
tag to set the page language based on the new language
variable.
Add the following.
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%-- Set language based on user's choice --%> <c:if test="${!empty language}"> <fmt:setLocale value="${language}" scope="session" /> </c:if> <%@page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">Since the
language
variable is only created when the user clicks the
link in the language toggle, you perform a test using
<c:if>
tags to determine whether the variable exists before attempting to set the language.
When applying the <fmt:setLocale>
tag, you set its scope to
session
as you want the user-selected language to take precedence
for the remainder of his or her session on the website. Also, since this is the
first time the fmt
library is used in the header, you declare the tag
library.
You can read the EL expression ${!empty language}
as,
"False if the language
variable is null or an empty string."
See the
Java
EE 5 Tutorial: Examples of EL Expressions for other available examples.
<fmt:setLocale>
tag, the toggle displays according to the language
specified by that value. (You can determine this value using the
${sessionScope['javax.servlet.jsp.jstl.fmt.locale.session']}
expression.)
<c:choose>
tags, and
create logic similar to the current implementation in the event that the locale has
been manually set. (Changes are displayed in bold.)
<div class="headerWidget"> <%-- language selection widget --%> <c:choose> <%-- When user hasn't explicitly set language, render toggle according to browser's preferred locale --%> <c:when test="${empty sessionScope['javax.servlet.jsp.jstl.fmt.locale.session']}"> <c:choose> <c:when test="${pageContext.request.locale.language ne 'cs'}"> english </c:when> <c:otherwise> <c:url var="url" value="chooseLanguage"> <c:param name="language" value="en"/> </c:url> <div class="bubble"><a href="${url}">english</a></div> </c:otherwise> </c:choose> | <c:choose> <c:when test="${pageContext.request.locale.language eq 'cs'}"> česky </c:when> <c:otherwise> <c:url var="url" value="chooseLanguage"> <c:param name="language" value="cs"/> </c:url> <div class="bubble"><a href="${url}">česky</a></div> </c:otherwise> </c:choose> </c:when> <%-- Otherwise, render widget according to the set locale --%> <c:otherwise> <c:choose> <c:when test="${sessionScope['javax.servlet.jsp.jstl.fmt.locale.session'] ne 'cs'}"> english </c:when> <c:otherwise> <c:url var="url" value="chooseLanguage"> <c:param name="language" value="en"/> </c:url> <div class="bubble"><a href="${url}">english</a></div> </c:otherwise> </c:choose> | <c:choose> <c:when test="${sessionScope['javax.servlet.jsp.jstl.fmt.locale.session'] eq 'cs'}"> česky </c:when> <c:otherwise> <c:url var="url" value="chooseLanguage"> <c:param name="language" value="cs"/> </c:url> <div class="bubble"><a href="${url}">česky</a></div> </c:otherwise> </c:choose> </c:otherwise> </c:choose> </div>
<fmt:setLocale>
tag. Add the following code
beneath the test you created earlier.
<p style="text-align: left;"><strong>tests:</strong> <br> <code>\${pageContext.request.locale.language}</code>: ${pageContext.request.locale.language} <br> <code>\${sessionScope['javax.servlet.jsp.jstl.fmt.locale.session']}</code>: ${sessionScope['javax.servlet.jsp.jstl.fmt.locale.session']} </p>
javax.servlet.jsp.jstl.fmt.locale.session
is the string
literal key for the Locale
set by the <fmt:setLocale>
tag. You can verify this by clicking in the editor's left margin to set a breakpoint (
) on the new test, then running the debugger (
) on the project. When you click the toggle link to change
languages in the browser, examine the Variables window (Alt-Shift-1; Ctrl-Shift-1 on
Mac) when the debugger suspends on the breakpoint.
EL expressions presented in this tutorial primarily use dot (.
) notation. The
format depicted in the expression above is known as bracket ([]
) notation
whereby you enter the string literal key within quotes in order to extract the object's value:
${sessionScope['javax.servlet.jsp.jstl.fmt.locale.session']}
Numerous EL resolver classes exist for the purpose of resolving expressions.
For example, when the above expression is encountered at runtime, the
ImplicitObjectResolver
first returns a Map
that maps session-scoped attribute names to their values. (In the
above image of the Variables window, you can verify that session attributes are maintained in a
ConcurrentHashMap
.)
In order to resolve the remainder of the expression, the
MapELResolver
is used to get the value of the key named 'javax.servlet.jsp.jstl.fmt.locale.session
'.
For more information, refer to the Java EE 5 Tutorial:
Unified
Expression Language: Resolving Expressions.
cs
) as the browser's preferred
language from the Accept-Language
HTTP header. This is indicated from the first
test. The page displays in Czech, and the language toggle enables the user to choose English.
The second test remains blank as the <fmt:setLocale>
tag has not yet been
called.<fmt:setLocale>
tag implemented in the header.jspf
file.
Although the browser's preferred language remains Czech, you see that the page now displays
according to the new language made available by the language toggle.Accept-Language
HTTP header, but is the language specified from the <fmt:setLocale>
tag.header.jspf
file.
(Deleted code in <body><%-- Language tests --%><p style="text-align: left;"><strong>tests:</strong><br><code>\${pageContext.request.locale.language}</code>: ${pageContext.request.locale.language}<br><code>\${sessionScope['javax.servlet.jsp.jstl.fmt.locale.session']}</code>: ${sessionScope['javax.servlet.jsp.jstl.fmt.locale.session']}</p><div id="main">
One of the implementation details which you have agreed on with the Affable Bean staff is that when the language toggle is used to change the language, the user remains in the same page view. In our current implementation, the welcome page is returned whenever the language toggle is clicked. A more user-friendly approach would be to provide the application with a means of tracking the request page view, and forwarding the request to that page view when the language toggle link is clicked.
We can accomplish this by setting a session-scoped view
attribute within each
of the page views, then referencing this attribute in the ControllerServlet
in order to determine where to forward the request. There are however several caveats
to consider when dealing with the language toggle in the confirmation page. These are
discussed and dealt with in steps 7-11 below.
Begin this exercise with snapshot
9 of the AffableBean
project. This snapshot includes completed English
and Czech resource bundles for all page views, all page views have been modified to use
text from the resource bundles, and the language toggle is presented in a state corresponding
to this point in the tutorial.
If you receive an error when running the project, revisit the setup instructions, which describe how to prepare the database and establish connectivity between the IDE, GlassFish, and MySQL.
<c:set>
tags to set a session-scoped view
attribute for each of the page views.
Open each of the page views in the editor and add the following code to the top of
each file.
<%-- Set session-scoped variable to track the view user is coming from. This is used by the language mechanism in the Controller so that users view the same page when switching between English and Czech. --%> <c:set var='view' value='/index' scope='session' />
<%-- Set session-scoped variable to track the view user is coming from. This is used by the language mechanism in the Controller so that users view the same page when switching between English and Czech. --%> <c:set var='view' value='/category' scope='session' />
<%-- Set session-scoped variable to track the view user is coming from. This is used by the language mechanism in the Controller so that users view the same page when switching between English and Czech. --%> <c:set var='view' value='/cart' scope='session' />
<%-- Set session-scoped variable to track the view user is coming from. This is used by the language mechanism in the Controller so that users view the same page when switching between English and Czech. --%> <c:set var='view' value='/checkout' scope='session' />Based on customer-agreed implementation details, we do not need to provide a means of switching languages on the confirmation page view. From a usability perspective, a user will have already selected his or her preferred language prior to checkout. From an implementation perspective, recall that we destroy the user session upon a successfully completed order. (Refer back to the final paragraph in Managing Sessions, which describes how to apply the
invalidate
method
to explicitly terminate a user session.) If the Affable Bean staff were to insist
on allowing customers to view their orders bilingually, you would need to consider
the following scenarios, dependent on whether you destroy the user session
upon displaying the confirmation page:
chooseLanguage
request
from the confirmation page refers to the appropriate order, and can
display customer-sensitive details in a secure fashion.ControllerServlet
in the editor. (If already opened,
press Ctrl-Tab and choose the file.) In the opened file, locate the portion
of the doGet
method that handles the chooseLanguage
request (line 126).
chooseLanguage
requests are forwarded to
the index.jsp
welcome page.
// if user switches language } else if (userPath.equals("/chooseLanguage")) { // get language choice String language = request.getParameter("language"); // place in session scope session.setAttribute("language", language); // forward request to welcome page try { request.getRequestDispatcher("/index.jsp").forward(request, response); } catch (Exception ex) { ex.printStackTrace(); } return; }
view
session attribute to forward the request back
to the originating page view. Make the following changes (in bold).
// if user switches language } else if (userPath.equals("/chooseLanguage")) { // get language choice String language = request.getParameter("language"); // place in request scope request.setAttribute("language", language); String userView = (String) session.getAttribute("view"); if ((userView != null) && (!userView.equals("/index"))) { // index.jsp exists outside 'view' folder // so must be forwarded separately userPath = userView; } else { // if previous view is index or cannot be determined, send user to welcome page try { request.getRequestDispatcher("/index.jsp").forward(request, response); } catch (Exception ex) { ex.printStackTrace(); } return; } }In the above implementation, you extract the value of the
view
attribute
and, provided that the view:
index.jsp
does not
reside in the same location as other page views, and therefore cannot be
resolved using the doGet
method's way of forwarding requests)doGet
method's userPath
variable, and forward the request using the method's existing RequestDispatcher
:
// use RequestDispatcher to forward request internally String url = "/WEB-INF/view" + userPath + ".jsp"; try { request.getRequestDispatcher(url).forward(request, response); } catch (Exception ex) { ex.printStackTrace(); }
/confirmation
'.
header.jspf
file in the editor and surround the language
toggle with the following test. You can use JSTL's functions (i.e.,
fn
)
library to perform string operations.
<div class="headerWidget"> <%-- If servlet path contains '/confirmation', do not display language toggle --%> <c:if test="${!fn:contains(pageContext.request.servletPath,'/confirmation')}"> <%-- language selection widget --%> <c:choose> ... </c:choose> </c:if> </div>Examine the above code snippet and note the following points:
HttpServletRequest
using the getServletPath
method. Because we use a RequestDispatcher
to forward the request
to the confirmation page (ControllerServlet
, line 158), the servlet
path becomes:
/WEB-INF/view/confirmation.jsp
pageContext.request.servletPath
EL expression is
comparable to calling request.getServletPath()
from a servlet.fn:contains()
function allows you to test if an input string contains the specified substring.fn
tag library has already been declared for you at the top of
in the header.jspf
file in snapshot 9:
<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
ControllerServlet
destroys
the user session and consequently the session-scoped locale that was set using the
<fmt:setLocale>
tag is also lost.
ControllerServlet
and locate the invalidate()
method which is used to destroy user sessions (approximately line 259).
Use the editor's quick search facility: press Ctrl-F (⌘-F on Mac)
and type in 'invalidate
'.
language
attribute to the locale value after the
session has been destroyed. (Changes in bold.)
// if order processed successfully send user to confirmation page if (orderId != 0) { // in case language was set using toggle, get language choice before destroying session Locale locale = (Locale) session.getAttribute("javax.servlet.jsp.jstl.fmt.locale.session"); String language = ""; if (locale != null) { language = (String) locale.getLanguage(); } // dissociate shopping cart from session cart = null; // end session session.invalidate(); if (!language.isEmpty()) { // if user changed language using the toggle, // reset the language attribute - otherwise request.setAttribute("language", language); // language will be switched on confirmation page! } // get order details Map orderMap = orderManager.getOrderDetails(orderId); ... userPath = "/confirmation"; }
You have now successfully integrated language support into the AffableBean
application according to customer specification. You've factored out all text from page views,
placed it into resource bundles, and have applied JSTL's fmt
tag library to
use resource bundle content based on the user's preferred language. You also implemented
a language toggle that enables users to switch between English and Czech, and override their
browser's default language choice. Download and examine
snapshot
10 to compare your work with the state of the project at the end of this tutorial unit.