by Christian Cryder, Barracuda ArchitectNOTE: This article is part 2 of a 4-part series originally published in
the Lutris Developers Journal.
In the last edition of Lutris Developers Journal, we began with an Introduction to Barracuda in which we identified four major
features of the Barracuda Presentation Framework -- Barracuda provides a Component
Model, an Event Model, a Form Mapping & Validation
framework, and Localization Services.
Each of these subsystems is loosely coupled, which means that you can use as much or as
little of Barracuda as you wish. If you have lots of simple pages, but you need to support
many different locales, then the Localization Services might be the only aspect that you
need. Or, if you are going to do lots of form processing, you may wish to leverage Form
Mapping & Validation. Large complex applications will probably benefit from using all
of the available subsystems.
The primary point here is that Barracuda doesn't try to make any of these decisions for
you -- you get to decide what makes sense for your particular needs and what doesn't.
Another advantage of this loose coupling is that it makes the system a lot easier to learn
because we can study one layer at a time.
Beginning with Localization Services
We will start with Localization Services, since this is an area that is easy to
understand and applicable to most webapps. Let's begin the discussion by reviewing the
manual approach to localizing an app. Suppose we have a sample application that uses a
login screen template (ie. Login.html) that looks something like this:
<form name="LoginForm" method="POST" action="DoLogin.event">
<h2>Please Log In!</h2>
<p>Before you can access your account information, you must first log in.
Please enter your Login and Password below and then press the 'Login' button.
If you do not have an account, please 'Register' to create one.</p>
<p><font color="#FF0000">
<span id="ErrorText">Optional Error Text Goes Here</span>
</font>
<table summary="Login Form">
<tr>
<td align="right">Login: </td>
<td><input id="login" size="20"> </td>
</tr>
<tr>
<td align="right">Password: </td>
<td><input type="password" id="password" size="20"> </td>
</tr>
<tr>
<td>
<input type="submit" value="Login" name="Login">
<input type="submit" value="Register" name="Register">
</td>
</tr>
</table>
</form>
The markup in green represents text that will be human readable when rendered in an
HTML browser.
Now, let's say we wanted to create an application that would use this template. How
would we go about it? Well, we'd probably begin by compiling the template into a DOM
object using XMLC. This would allow us to programmatically load the template and populate
it accordingly -- depending on whether or not the user has already tried to login, we may
or may not want to show the error message, we might want to prepopulate the input fields
with the last values entered, etc. Once the DOM has been manipulated as needed, we then
simply render it back to the client's browser.
The Manual Localization Strategy - 2 Approaches
Ok, so far so good. But what if we wanted to support users who are running in different
client locales? For instance, what if the user's locale is Spanish? In this case, we'd
need several additional steps. First, we have to determine which locale the client is
running in. Then we have to react accordingly. There are two different strategies we can
choose from.
The first option is to simply create another template (ie. Login_spanish.html) that
looks identical to the first and then manually localize the file. This makes the
developer's task fairly straightforward--simply determine the target client locale and
load the appropriate template. Unfortunately there are several problems with this
approach. Whenever the designer makes a change to one template he must remember to make
that change in all the other templates as well. The resulting maintenance overhead--along
with the opportunity for introducing errors--quickly becomes onerous. In addition, what
happens if the client is running a locale not explicitly supported by the server (say
Finnish)? Adding new locales will always require coding changes.
A second option is to simply use one template and set all the text dynamically at
runtime. This approach is much less burdensome for the designer, but is unfortunately
tremendously inefficient for the server. Under this approach, every time we receive a
request for a page we would load the DOM template and then dynamically set every single
text element in the page (even if the locale was unchanged from the previous request).
This added overhead significantly impacts performance. In addition, we still face the
issue of handling clients with unsupported locales. While we could probably improve things
a little with some kind of strategic cache-and-reuse-when-possible strategy, the basic
architecture here is fundamentally suspect.
What we really need is a solution that makes localization easy for both the designer
and developer.
Stealing a Page from Resource Bundles
At this point, it may be helpful to step back and take a look at how Java handles
localized resources. It turns out that the Sun engineers have created an elegant
solution to a similar problem; by examining their approach we might be able to learn
something useful.
The java.util library defines two classes that are most interesting to
our current discussion. The Locale class is used to represent a target
Locale. It consists of a Language, Country, and (optionally) Variant codes. A locale could
be something as broad as "English" or as specific as "English, US,
Creole". As you can see, this allows us to cover just about all client locales. The
other class pertinent to our discussion is ResourceBundle. While we don't
need to know the all details of this class, the main point to understand is that this
class is used to access a resource (like a property file) based on a specific locale.
The way these two classes work together is quite simple. Let's say I have 4 property
files:
- foo.properties (default)
- foo_en.properties (English version)
- foo_en_US.properties (English version, United States)
- foo_es.properties (Spanish version)
When I ask the ResourceBundle to load the "foo" properties file, it looks at
the target locale and figures out which physical file is the closest possible match. For
instance, if I pass in a locale for "English, US, Creole", I will get back a
reference to foo_en_US.properties. If I pass in a reference to a "Spanish"
locale, I will get back the Spanish version of the file. If I request a locale for which
there are no matches (say Finnish) then it will return the default.
This is really nice for the developer because I only have to know the name of the base
resource ("foo") and the target locale. This makes it easy to write code that
doesn't need to be recompiled every time we need to support and additional locale.
Applying this Concept to XMLC Templates
Wouldn't it be slick if we could load XMLC generated DOM templates in the same way that
we can load ResourceBundles? With Barracuda we can. Barracuda provides a specialized DefaultDOMLoader
that allows you to specify an XMLC template (ie. FooHTML.class) along with the target
client locale ( "English, US, Creole"). This class then goes through a matching
process similar to that of ResourceBundle to find the closest matching template. If no
match exists, the default will be returned.
This certainly is convenient for the developer--we can load a class based on client
locale and not have to worry about whether or not the specific template actually exists or
not. But what about the designer? How do we avoid the maintenance nightmare of supporting
separate *ML templates for each supported locale? It turn out Barracuda can help here as
well.
The Barracuda Localization Taskdef
Barracuda provides a custom Ant taskdef that will automatically create localized
versions of any given template. For instance, let's say that we have a template called Login.html
(see above). All the designer has to do is to create a simple properties file called Login.properties
that contains references to all the text which can be localized. If we were to return to
our initial Login example, the file might look something like this:
Login.Data.Header = Please Log In!
Login.Data.Expl = Before you can access your account...
Login.Data.Error = Optional Error Text Goes Here
Login.Label.Login = Login:
Login.Label.Password = Password:
Login.Input.Login = Login
Login.Input.Register = Register
The keys we are using here are totally arbitrary. The only requirement is that they be
unique. It is essential, however, that the text match the master template exactly, as this
is what the tasdef will look for.
Now all we need to do is create custom properties files for the specific locales we
wish to support. For instance, let's say we want to support Spanish users. All we have to
do is create a Login_es.properties file that defines localized values for
the keys defined in the master properties file:
Login.Data.Header = Ingrese Por favor!
Login.Data.Expl = Antes de que pueda acceder su cuenta...
Login.Label.Login = Usuario:
Login.Label.Password = Clave:
Login.Input.Login = Usuario
Login.Input.Register = Registrarse
When the localization taskdef compiles the XMLC templates it not only creates a LoginHTML.class
but it also detects the presence of the Spanish properties file from which it then
automatically creates a LoginHTML_es.class. Any values in the localized
properties file will be automatically inserted into the template. Any values which are
missing (in this case, Login.Data.Error, will be left alone, effectively defaulting the
value to the contents of the master file...just what we'd expect with a ResourceBundle!).
Now if we use the Barracuda DOM Loader class and specify a Spanish locale, we'll get a
reference to the Spanish template. And all this happens automatically!
Putting it All together
Ok, let's review the localization process.
The designer creates one master template (Login.html) and then places
localized content in separate properties files (Login.properties, Login_es.properties,
etc.).
When the system gets compiled multiple XMLC objects will get created
(LoginHTML.class, LoginHTML_es.class, etc.).
To use the templates in a webapp, the developer simply asks Barracuda to
determine the client locale (based on an HTTP Request) and then uses the DefaultDOMLoader
to load the localized version of a target template.
That's it! Barracuda takes care of the dirty work, creating and loading the proper
localized templates as needed. The whole process is easy to implement -- and maintain --
for both designer and developer.
Getting More Information
If you'd like to learn more about Barracuda Localization, you might want to check out
the Barracuda
Localization Tutorial or browse the project documentation at http://barracuda.enhydra.org. In the next issue,
we'll take a peek at the Barracuda Component Model! |