Some web applications will want to react to their user‘s browser language. Here is a short guide on how to do this using a standard JakartaEE restfulWS-based app on Open Liberty.
Step 1: Create a filter
The first thing you will want to do is to create a filter to save away your user‘s accepted language. Why a servlet filter? A filter allows an abstraction from all servlets and controllers. You would not want to react to the user‘s language individually.
Luckily, the Variant
class is included in JakartaEE. Here is, how I store the user‘s preferred language in the servlet context:
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Variant;
import jakarta.ws.rs.ext.Provider;
import java.util.List;
import java.util.Locale;
@Provider
public class LanguageFilter implements ContainerRequestFilter, ContainerResponseFilter {
private static final String LANG = "LanguageFilter.lang";
public static final List<Variant> VARIANTS = Variant.VariantListBuilder.newInstance()
.languages(Locale.ENGLISH, Locale.GERMAN)
.build();
@Override
public void filter(ContainerRequestContext requestContext) {
Variant variant = requestContext.getRequest().selectVariant(VARIANTS);
if (variant == null) {
// Error, respond with 406
requestContext.abortWith(Response.notAcceptable(VARIANTS).build());
} else {
// keep the resolved lang around for the response
requestContext.setProperty(LANG, variant.getLanguageString());
}
}
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
String lang = (String) requestContext.getProperty(LANG);
responseContext.getHeaders().putSingle(HttpHeaders.CONTENT_LANGUAGE, lang);
}
}
This filter will extract the preferred language on incoming requests and re-set it (if available) on outgoing requests.
Create a message bundle
Yes, a good old plain Java message bundle! They are still around and will work for what we want to do here.
Here‘s an example:
de/bmarwell/messages.properties
help.generic = Welcome to the dice parser!
de/bmarwell/messages_de.properties
help.generic = Willkommen beim Würfel-Parser!
Create an injectable MessageProvider CDI bean
To be able to use a single message provider, you can use this class:
import jakarta.enterprise.context.ApplicationScoped;
import java.io.Serial;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.ConcurrentHashMap;
@ApplicationScoped
public class ResourceBundleMessageProvider implements MessageProvider {
@Override
public String getString(String bundleName, Locale locale, String helpKey) {
try {
final ResourceBundle resourceBundle = ResourceBundle.getBundle(bundleName, locale);
return resourceBundle.getString(helpKey);
} catch (MissingResourceException missingResourceException) {
return "";
}
}
}
Of course this needs some heave tweaking before such a class could go into production. Defaults, anyone? But for the sake of showing how to react to preferred languages, this will do for now.
Load messages
Now with resources and a CDI bean at hand, you can load the localized strings wherever you like:
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Request;
import jakarta.ws.rs.core.UriInfo;
@Path("/")
@ApplicationScoped
public class HelpResource {
@Inject
private MessageProvider msgProvider;
@GET
@Produces(MediaType.TEXT_PLAIN)
public String help(@Context Request request, @Context UriInfo uriInfo) {
return msgProvider.getString(
"messages",
request.selectVariant(LanguageFilter.VARIANTS).getLanguage(),
"help.generic"
);
}
}
Conclusion
It is easy to get started with localized applications. Just be sure to extend on the idea, as this is not production ready.
More features you might want to see in your application are:
react to user agents differently.
the same language, but different countries.
format strings with placeholders like
%s
.
However, JAX-RS or RestfulWS will already have prepopulated the variant
http header for your. Neat!