If you’re writing a Liferay Application, you’re probably a genius who is also really cool, which means your application will be used throughout the entire world. At least, if its messages can be translated into their language, it will. Thankfully, Liferay makes it easy to support translation of your application’s language keys.
You just need to create a default language properties file
Language.properties) and one for each translation you’d like to support (for
Language_fr.properties for your French translation), and put them in
the correct location in your application. Use the two letter locale that
corresponds to the language you want to translate in your file names (for example,
Language_es.properties provides a Spanish translation for each key).
Application localization topics:
- What are Language Keys?
- What Locales are Available By Default?
- Where do I Put Language Files?
- Creating a Language Module
- Using a Language Module
- Using Liferay DXP’s Language Properties
Each language property file holds key/value pairs. The key is the same in all
the language property files, while the value is translated in each file. You
specify the key in your user interface code, and the appropriately translated
message is returned automatically for your users, depending on the locale being
used in Liferay. If you have Liferay running locally, append the URL with
a supported locale to see the translations (for example, enter
Language keys are just keys to use in place of a hard coded, fully translated
String value in your user interface code. You use a language key in your JSP
<liferay-ui:message /> tag.
If you wanted to hard code a message, you’d use the tag like this:
<liferay-ui:message key="Howdy, Partner!" />
In that case you’ll get a properly capitalized and punctuated message in your application.
Instead, specify a simple key instead of the final value:
<liferay-ui:message key="howdy-partner" />
That way you can provide a translation of the key in a default language
properties file (
Either way, you get the same output. The properties file lets you put all your messages in one place, and you can add additional language properties files with translations later. You just need to make sure there’s a locale that corresponds to your translation.
The values from your default
Language.properties file appear if no locale is
specified. If a locale is specified, a key from a file corresponding to that
local is retrieved. For example, if a Spanish translation is sought,
Language_es.properties file must be present to provide the proper values. If
it isn’t, the default language properties (from the
There are a bunch of locales available by default in Liferay. Look in the
to find them.
locales=ar_SA,eu_ES,bg_BG,ca_AD,ca_ES,zh_CN,zh_TW,hr_HR,cs_CZ,da_DK,nl_NL, nl_BE,en_US,en_GB,en_AU,et_EE,fi_FI,fr_FR,fr_CA,gl_ES,de_DE,el_GR, iw_IL,hi_IN,hu_HU,in_ID,it_IT,ja_JP,ko_KR,lo_LA,lt_LT,nb_NO,fa_IR, pl_PL,pt_BR,pt_PT,ro_RO,ru_RU,sr_RS,sr_RS_latin,sl_SI,sk_SK,es_ES, sv_SE,tr_TR,uk_UA,vi_VN
To provide a translation for one of these locales, specify the locale in the
file name containing the translated keys (for example,
holds the Spanish translation).
In an application with only one module that holds all your application’s views
(for example, all its JSPs) and portlet components, create
src/main/resources/content folder in that module, and place your
Language_xx.properties files there.
After that, make sure any portlet components (the
-Portlet classes) in the module include this property:
Providing translated language properties files and specifying the
javax.portlet.resource-bundle property in your portlet component is all you
must do to point Liferay DXP at your translations. Users see the translations for
the locales they select.
In a more complicated, well-modularized application, you might have language keys spread over multiple modules providing portlet components and JSP files. Moreover, there might be a fair number of duplicated language keys between the modules. Thankfully you don’t need to maintain language properties files in each module.
If you’re crazy about modularity (and you should be), you might have an application with multiple modules that provide the view layer. These modules are often called web modules.
my-application/ my-application-web/ my-admin-application-web/ my-application-content-web/ my-application-api/ my-application-service/
Each of these modules can have language keys and translations to maintain, and there will probably be duplicate keys. You don’t want to end up with different values for the same key, and you don’t want to maintain language keys in multiple places. In this case, you need to go even crazier with modularity and create a new module, which we’ll call a language module.
In the root project folder (the one that holds your service, API, and web
create a new module
to hold your app’s language keys. For example, here’s the folder structure of a
language module called
my-application-lang/ bnd.bnd src/ main/ resources/ content/ Language.properties Language_ar.properties Language_bg.properties ...
In the language module, create a
src/main/resources/content folder. Put your
language properties files here. A
Language.properties file might look like
application=My Application add-entity=Add Entity
Create any translations you want, adding the translation locale ID to the
language file name. File
Language_es.properties might look like this:
my-app-title=Mi Aplicación add-entity=Añadir Entity
On building the language module, Liferay DXP’s
ResourceBundleLoaderAnalyzerPlugin detects the
file and adds a resource bundle
to the module. A capability is a contract a module declares to Liferay DXP’s OSGi
framework. Capabilities let you associate services with modules that provide
them. In this case, Liferay DXP registers a
service for the resource bundle capability.
Next, you’ll configure a web module to use the language module resource bundle.
A module or traditional Liferay plugin can use a resource bundle from another
module and optionally include its own resource bundle. OSGi manifest headers
Provide-Capability make this possible, and it’s
especially easy in modules generated from Liferay project templates.
Instructions for using a language module are divided into these environments:
If you’re using bnd with Maven or Gradle, you need only specify Liferay’s
-liferay-aggregate-resource-bundle: bnd instruction–at build time, Liferay’s
bnd plugin converts the instruction to
Provide-Capability parameters automatically. Both approaches are demonstrated.
Modules generated from Liferay project templates have a Liferay bnd build time
-liferay-aggregate-resource-bundles. It lets you use other
resource bundles (e.g., including their language keys) along with your own.
Here’s how to do it:
Open your module’s
-liferay-aggregate-resource-bundles:bnd instruction and assign it the bundle symbolic names of modules whose resource bundles to aggregate with the current module’s resource bundle.
-liferay-aggregate-resource-bundles: \ [bundle.symbolic.name1],\ [bundle.symbolic.name2]
For example, a module that uses resource bundles from modules
would set this in its
-liferay-aggregate-resource-bundles: \ com.liferay.docs.l10n.myapp1.lang,\ com.liferay.docs.l10n.myapp2.lang
The current module’s resource bundle is prioritized over those of the listed modules.
At build time, Liferay’s bnd plugin converts the bnd instruction to
Provide-Capability parameters automatically. In
traditional Liferay plugins, you must specify the parameters manually.
To use a language module, from a traditional Liferay plugin you must specify the
language module using
manifest headers in the plugin’s
Follow these steps to configure your traditional plugin to use a language module:
Open the plugin’s
liferay-plugin-package.propertiesfile and add a
Require-Capabilityheader that filters on the language module’s resource bundle capability. For example, if the language module’s symbolic name is
myapp.lang, you’d specify the requirement like this:
In the same
liferay-plugin-package.propertiesfile, add a
Provide-Capabilityheader that adds the language module’s resource bundle as this plugin’s (the
myapp.webplugin) own resource bundle:
Provide-Capability:\ liferay.resource.bundle;resource.bundle.base.name="content.Language",\ liferay.resource.bundle;resource.bundle.aggregate:String="(bundle.symbolic.name=myapp.lang)";bundle.symbolic.name=myapp.web;resource.bundle.base.name="content.Language";service.ranking:Long="4";\ servlet.context.name=myapp-web
In this case, the
myapp.web plugin solely uses the language module’s resource
bundle—the resource bundle aggregate only includes language module
Aggregating resource bundles comes into play when you want to use a language module’s resource bundle in addition to your plugin’s resource bundle. These instructions show you how to do this, while prioritizing your current plugin’s resource bundle over the language module resource bundle. In this way, the language module’s language keys compliment your plugin’s language keys.
For example, a portlet whose bundle symbolic name is
myapp.web uses keys from
myapp.lang, in addition to its own. The portlet’s
Web-ContextPath OSGi headers accomplish this.
Provide-Capability:\ liferay.resource.bundle;resource.bundle.base.name="content.Language",\ liferay.resource.bundle;resource.bundle.aggregate:String="(bundle.symbolic.name=myapp.web),(bundle.symbolic.name=myapp.lang)";bundle.symbolic.name=myapp.web;resource.bundle.base.name="content.Language";service.ranking:Long="4";\ servlet.context.name=myapp-web
Provide-Capability header has two parts:
liferay.resource.bundle;resource.bundle.base.name="content.Language"declares that the module provides a resource bundle whose base name is
liferay.resource.bundle;resource.bundle.aggregate:String=...directive specifies the list of bundles whose resource bundles are aggregated, the target bundle, the target bundle’s resource bundle name, and this service’s ranking:
"(bundle.symbolic.name=myapp.web),(bundle.symbolic.name=myapp.lang)": The service aggregates resource bundles from bundles
bundle.symbolic.name=myapp.web(the current module) and
bundle.symbolic.name=myapp.lang. Aggregate as many bundles as desired. Listed bundles are prioritized in descending order.
bundle.symbolic.name=myapp.web;resource.bundle.base.name="content.Language": Override the
myapp.webbundle’s resource bundle named
service.ranking:Long="4": The resource bundle’s service ranking is
4. The OSGi framework applies this service if it outranks all other resource bundle services that target
servlet.context.name=myapp-web: The target resource bundle is in servlet context
Now the language keys from the aggregated resource bundles compliment your plugin’s language keys.
Did you know that Liferay DXP’s core language keys are also available to your module? They’re up next.
If you have Liferay DXP’s source code, you can check out Liferay DXP’s core language
properties by looking in the
portal-impl/src/main/content folder. Otherwise,
you can look in the
portal-impl.jar that’s in your Liferay bundle.
liferay-portal/portal-impl/src/content/Language_xx.properties [Liferay Home]/tomcat-[version]/webapps/ROOT/WEB-INF/lib/portal-impl.jar
These keys are available at runtime, so when you use any of Liferay DXP’s default keys in your user interface code, they’re automagically swapped out for the appropriately translated value. Using Liferay DXP’s keys where possible saves you time and ensures that your application follows Liferay’s UI conventions.
If you want to generate language files for each supported locale automatically, or to configure your application to generate translations automatically using the Microsoft Translator API, check out the tutorial Automatically Generating Language Files.