Struts2 framework supports internationalization and we can create resource bundle property files to be used by the framework. Struts2 i18n is used a lot in creating labels based on the locale in result pages using UI tags or to show action messages or errors when action classes implement ValidationAware interface or extend ActionSupport class.
Struts2 framework supports i18n through I18nInterceptor
interceptor and we can pass locale in request with parameter request_locale. This interceptor is part of defaultStack interceptor stack, so we don’t need to do anything for localization. However we can override locale request parameter with parameterName parameter, we will look into this in our sample project.
When we pass localization key, Struts 2 framework looks for resource bundles at various places in below order:
- {ActionClassName}.properties and it should be in the same package with Action Class.
- Interface.properties (every interface and sub-interface)
- BaseClass.properties (all the way to Object.properties)
- ModelDriven’s model (if implements ModelDriven)
- package.properties in the class package and then to the parent packages till the root
- global resource properties configured in struts property file
It’s good to have options but excessive options can lead to confusion. Also if we have so many property files for localization, it will add IO cost in searching the keys into them. Personally I always create only package and global properties files but sometimes when we need to show message specific to an action class, action class property files come handy.
We can get localized text by following options:
<s:property value="getText('key')" />
Above method can be used when action class is extending ActionSupport class, getText() method is defined in ActionSupport class.
<s:text name="key">Default Text</s:text>
We can use Struts text tag to get the localized string, if the key is not found then “Default Text” will be used.
<s:i18n name="global">
<s:text name="key"></s:text>
</s:i18n>
We can use i18n tag to specify the location of resource bundle from where the properties will be loaded. In above case, it will try to get key value from global.properties from WEB-INF/classes directory. If we know the location of keys, then it’s good to use i18n tag for better performance.
<s:textfield key="mykey" name="textfieldName"/>
Most of the UI tags have key attribute that we can use to get the localized text from resource bundle.
Let’s look into a simple web application where we are using Struts2 resource bundle and i18n support for generating localized response. Our final project will look like below image.
For our example, we will have localized texts for fr_FR and de_DE locales and we have ActionClass, package and global resource bundles. As you can see from above image that Action Class property files are in the same package whereas package property files are in the parent package. Global resource bundles are in the classes directory and we will configure it in struts configuration file.
Table of Contents
Configuration Files
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns="https://java.sun.com/xml/ns/javaee" xsi:schemaLocation="https://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>Struts2Localization</display-name>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
pom.xml
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>Struts2Localization</groupId>
<artifactId>Struts2Localization</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<warSourceDirectory>WebContent</warSourceDirectory>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
<finalName>${project.artifactId}</finalName>
</build>
<dependencies>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>2.3.15.1</version>
</dependency>
</dependencies>
</project>
Deployment descriptor and maven pom.xml files are simple and used to configure web application to use Struts2 framework.
struts.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"https://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<!-- constant to define result path locations to project root directory -->
<constant name="struts.convention.result.path" value="/"></constant>
<!-- constant to define global resource bundle -->
<constant name="struts.custom.i18n.resources" value="global"></constant>
<package name="user" namespace="/" extends="struts-default">
<action name="home">
<result>/home.jsp</result>
</action>
<action name="welcome" class="com.journaldev.struts2.actions.WelcomeAction">
<interceptor-ref name="defaultStack">
<param name="i18n.parameterName">appLocale</param>
</interceptor-ref>
<result name="success">/welcome.jsp</result>
</action>
</package>
</struts>
We are providing global resource bundle name as global and overriding the i18n interceptor locale request parameter to appLocale, we will use this in our request form parameters for our use case.
Action Class
package com.journaldev.struts2.actions;
import com.opensymphony.xwork2.ActionSupport;
public class WelcomeAction extends ActionSupport {
@Override
public String execute() {
return SUCCESS;
}
private String username;
private String address;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
Action class is simple and have only few java bean properties with getter and setter methods.
Resource Bundles
Global Resource Bundles
global.properties
#global labels
global.username=User Name
global.address=Address
global.submit=Submit
global.selectlocale=Select Locale
action.welcome.thankyou=Thank You
global_fr_FR.properties
#global labels
global.username=User Name (FR)
global.address=Address (FR)
global.submit=Submit (FR)
global.selectlocale=Select Locale (FR)
action.welcome.thankyou=Thank You (FR)
global_de_DE.properties
#global labels
global.username=User Name (DE)
global.address=Address (DE)
global.submit=Submit (DE)
global.selectlocale=Select Locale (DE)
action.welcome.thankyou=Thank You (DE)
Package Resource Bundles
package.properties
action.welcome.title=Welcome Page
action.welcome.thankyou=Thank You!!
package_fr_FR.properties
action.welcome.title=Welcome Page (FR)
action.welcome.thankyou=Thank You!! (FR)
package_de_DE.properties
action.welcome.title=Welcome Page (DE)
action.welcome.thankyou=Thank You!! (DE)
Action Class Resource Bundles
WelcomeAction.properties
#action specific labels
action.welcome.username=User Name
action.welcome.address=Address
WelcomeAction_fr_FR.properties
#action specific labels for locale fr_FR
action.welcome.username=User Name (FR)
action.welcome.address=Address (FR)
WelcomeAction_de_DE.properties
#action specific labels for locale de_DE
action.welcome.username=User Name (DE)
action.welcome.address=Address (DE)
JSP Pages
home.jsp
<%@ page language="java" contentType="text/html; charset=US-ASCII"
pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<title>Struts2 Localization Example</title>
</head>
<body>
<s:form action="welcome">
<s:textfield key="global.username" name="username"></s:textfield>
<s:textfield key="global.address" name="address"></s:textfield>
<s:select list="{'en_US','fr_FR','de_DE'}" name="appLocale" key="global.selectlocale"></s:select>
<s:submit key="global.submit" name="submit"></s:submit>
</s:form>
</body>
</html>
As you can see that we can provide locale from the select box in home.jsp and the variable name is same as configured in struts property file for locale parameter.
welcome.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><s:property value="getText('action.welcome.title')"/></title>
</head>
<body>
<s:property value="getText('action.welcome.username')"/>: <s:property value="username"/><br>
<s:text name="action.welcome.address"></s:text>: <s:property value="address"/><br><br>
<s:i18n name="global">
<s:text name="action.welcome.thankyou"></s:text>
</s:i18n>
</body>
</html>
welcome.jsp is showing usage of localization keys through i18n, text and property tag.
Now when we run our application, we get following response pages.
That’s all for Struts2 internationalization and resource bundle example, it’s very simple and easy to use. Make sure you have properly designed the application resource bundles otherwise it will become hard to maintain if the number of files are too much.
Download project from below link and play around with it for better understanding.
How about the images will load based on the locale ?
Localization does not work on page load. I have kept English and French in my page, but when I load the page it does not take user defined key-value pairs. In place of label, key is shown but when I click on English or French than it shows the correct value.