Localization in CUBA applications

CUBA applications have a build-in support for localized messages for different languages. Translations are externalized in properties files to have a clear separation between the application code and the actual translations.

What Will be Built

This guide enhances the CUBA Petclinic example to show how localized messages within the CUBA application can be defined and used:

  • the application is also available in German language

  • dynamic messages as automatically generated descriptions for visits are localized

Requirements

Your development environment requires to contain the following:

Download and unzip the source repository for this guide, or clone it using git:

Example: CUBA petclinic

The project that is the basis for this example is CUBA Petclinic. It is based on the commonly known Spring Petclinic. The CUBA Petclinic application deals with the domain of a Pet clinic and the associated business workflows to manage a pet clinic.

The underlying domain model for the application looks like this:

Domain model

The main entities are Pet and Visit. A Pet is visiting the petclinic and during this Visit a Vet is taking care of it. A Pet belongs to an Owner, which can hold multiple pets. The visit describes the act of a pet visiting the clinic with the help of its owner.

Where Translations are Stored in a CUBA Application

CUBA follows a common pattern for translations in Java-based applications. Messages for different languages / locales are stored next to the source-code within the same package in .properties files.

For every language and every package there exists a file containing information on how to translate a given text into the target language. The language files contain a suffix according to the locale code of the language (e.g. _ru for Russian, _es for Spanish).

Additionally there is a default file message.properties without a specified locale in the name. This file acts as a fallback in case the desired language is not available or a particular translation is not available.

When using localization dialogs in Studio, the Studio puts messages for the first selected locale to the default file and others to the files with suffixes.

If the application is international, it is reasonable to use default files for the language of the application primary audience or for the English language, so that the messages from these default files are displayed to the user if the messages in the required language are not found.

In the Petclinic example, this file structure looks like this:

com.haulmont.sample.petclinic.entity
|
├── owner
│   ├── Owner.java
│   └── messages.properties
├── pet
│   ├── Pet.java
│   ├── PetType.java
│   ├── messages.properties
│   └── messages_de.properties
├── vet
│   ├── Specialty.java
│   ├── Vet.java
│   ├── messages.properties
│   └── messages_de.properties
└── visit
    ├── Visit.java
    ├── VisitType.java
    ├── messages.properties
    └── messages_de.properties

The properties file consists of key-value pairs, where the key is the identifier of the Message that has to be localized and the value is the corresponding translation of the language. The key is the same for all language files, only the value differs. In the Petclinic example, the localized entity and entity attributes' names look like this:

messages.properties
Pet.birthDate = Birth date
Pet = Pet
Pet.type = Type
Pet.owner = Owner
Pet.identificationNumber = Identification Number
PetType = Pet Type
messages_de.properties
Pet.birthDate = Geburtstag
Pet = Haustier
Pet.type = Typ
Pet.owner = Tierhalter
Pet.identificationNumber = Haustier-Identifikationsnummer
PetType = Tierart

In the Petclinic example, the translations for the entity attributes have been made by creating the messages_de.properties files in the project.

CUBA Studio offers a Resouce Bundle view for every package. It allows you to see an overview over the translations for the selected package.

cuba studio resource bundle screen

Due to the side-to-side view in the Resouce Bundle Editor, creating translations is very comfortable. Missing translaslations for a particular message key are highlighted so that it is easy to spot them.

More information about the general structure of how localized messages are stored can be found in the reference documentation: Localization.

Register Available Languages in the Application

In order to activate the possibility to have a language selection for the user, the application needs two main configuration options in place.

The first one is the activation of the language drop-down in the login screen: cuba.localeSelectVisible. It has to be set to true.

The second configuration is the available languages / locales that should be available for selection: cuba.availableLocales = English|en;German|de.

Both configurations should be placed in the web-app.properties. After this configuration is in place, the Login screen displays the language selection box at login:

login screen with activated language selection

When a user is logging in with a given language, the application displays the correct translations. This happens automatically for all UI components if the corresponding translation files are in place. The <form> component renders the attribute captions in the correct language:

localized editor

Referencing Localized Messages

For the two cases of <groupTable /> and <form /> the translations are automatically derived from the entity attribute translations that should be displayed. For UI components or messages in the application that are not directly tied to an entity attribute, there are two ways of interacting with the Localization mechanisms of CUBA: either in the XML screen definitions or programmatically from the Java code.

In order to display the correct localized message, the message keys have to be referenced explicitly in the source code.

Referencing Messages in XML Descriptors

The first option is to reference a particular message key within the XML UI descriptor. In the Petclinic example, the caption of the "regular checkup" button should be displayed localized. This happens through the prefix msg:// followed by the translation key:

visit-browse.xml
<groupTable id="visitsTable"
            dataContainer="visitsDc"
            width="100%">
    <actions>

        <action id="createRegularCheckup"
                caption="msg://createRegularCheckup" (1)
                icon="font-icon:BRIEFCASE" />

    </actions>
    <buttonsPanel id="buttonsPanel"
                  alwaysVisible="true">
        <button id="createForPetBtn" action="visitsTable.createRegularCheckup"/>
    </buttonsPanel>
</groupTable>
1 msg://createRegularCheckup references the key createRegularCheckup in the current package of the XML descriptor

Programmatic Localization of Messages

The second way of interacting with the Localization mechanism is directly through the Java code. In the Petclinic example, the "regular checkup" functionality should render the following two dynamic messages programmatically:

  1. when a new Regular Checkup screen is opened, the description attribute for the visit entity should be pre-filled with a particular localized message

  2. once the Regular Checkup is created, a localized notification message should be rendered, containing the name of the pet

Messages can be received programmatically via the Messages Interface. It provides an API to fetch a translated String through the localization mechanism of CUBA for the locale of the current user.

In the example code instead of using the Messages Interface, the MessageBundle Interface is used. It is a new shorthand mechanism introduced in CUBA 7.0, that fetches a particular message from the message pack of the calling Java class. This way it is not required to provide the package for every translation API request.

The first localized message for pre-filling the description attribute is created as follows:

VisitBrowse.java
public class VisitBrowse extends StandardLookup<Visit> {

    @Inject
    private Notifications notifications;

    @Inject
    private MessageBundle messageBundle;
    // ...
    private void createVisitForPet(Pet pet) {
        screenBuilders.editor(visitsTable)
                .newEntity()
                .withInitializer(visit -> {
                    visit.setDescription(
                        regularCheckupDescriptionContent(pet) (1)
                    );
                    visit.setPet(pet);
                })
                .withScreenClass(RegularCheckup.class)
                .withLaunchMode(OpenMode.DIALOG)
                .withAfterCloseListener(event -> {
                    if (event.getCloseAction().equals(WINDOW_COMMIT_AND_CLOSE_ACTION)) {
                        showRegularCheckupCreatedNotification(pet);
                    }
                })
                .build()
                .show();
    }

    private String regularCheckupDescriptionContent(Pet pet) {
        return messageBundle.formatMessage( (2)
                "regularCheckupContent", (3)
                pet.getName(), (4)
                pet.getIdentificationNumber()
        );
    }
1 the description of the visit instance will be set on entity creation
2 the messageBundle bean is responsible for fetching the translated message
3 regularCheckupContent is the key of the message to be translated
4 the parameters pet.getName() and pet.getIdentificationNumber() are passed to the translation as dynamic parts

For the dynamic parts of a message, the translation should contain the placeholder %s like in this definition:

messages_de.properties
regularCheckupContent=Kontrolluntersuchung für %s (%s)\n\n

Bestandteile: \n
- Krallen-Kontrolle\n
- Temperatur messen\n
- Zahn-Check

This code results into the following description value of the regular check:

editor with programmatic created i10n messages

The second message should be displayed as a notification once the Regular Checkup was successfully created. There is the method showRegularCheckupCreatedNotification in the Visit browser, that will be called once the instance is created:

VisitBrowse.java
public class VisitBrowse extends StandardLookup<Visit> {
    // ...
    @Inject
    private MetadataTools metadataTools;

    @Inject
    private MessageBundle messageBundle;

    @Inject
    private Notifications notifications;

    private void showRegularCheckupCreatedNotification(Pet pet) {

        String petName = metadataTools.getInstanceName(pet); (1)

        String regularCheckupCreatedMessage = messageBundle
            .formatMessage("regularCheckupCreated", petName); (2)

        notifications.create(Notifications.NotificationType.TRAY)
                .withCaption(regularCheckupCreatedMessage) (3)
                .show();
    }
1 metadataTools is used in CUBA 7.0+ to fetch the instance name from an entity
2 The messageBundle is used once again to get a particular message with a parameter
3 the localized Message String is passed to the notifications bean to render a tray notification

Translation of Existing UI Screens / Functionality

Since CUBA comes with certain UI components and with multiple UI screens out of the box, this existing functionality also needs to be localized in order to have a fully translated application. For this problem, there are two main solutions:

  1. Community Languages Packs

  2. manually provided translations

Community Language Packs

The first and easiest solution is to use existing language packs that are created by the community. There are currently twelve Translations available including Spanisch, Russian, French, Chinese, German and a lot more.

The Community Translations Packs are provided by the community, therefore the accuracy and the catch up with the latest changes in the platform differ and are provided as best effort.

In order to get those translations into the Application they can be fetched manually from the Translations Github repository. Some Translations are also directly available via an application component through the CUBA marketplace, which makes the installation as easy as installing any other application component.

In the Petclinic example, as the destination language is german, there is already an existing translation application component available: German Localization which is utilized.

The application component can be installed in CUBA Studio via the following menu:

CUBA > Project Properties > Custom Components.

cuba studio add translation component

Afterwards, the only thing to do is to add the new language to the application property cuba.availableLocales manually. Alternatively this can also be done via the the above shown Project Properties screen in CUBA Studio.

Manually Provided Translations

In case the target language is not available through the community or some translations are missing or are not accurate, it is also possible to provide those translations directly within the application.

CUBA itself provides translations for the screens directly in the source code of the platform as regular *.properties files. The User browse screen for example contains the messages.properties file in the package com.haulmont.cuba.gui.app.security.user.browse, which contains the translations for the screen.

In order to provide / override translations for this screen, two things have to be done:

Create GUI module if Necessary

The destination module of where the translations have to be placed has to be available in the application. The majority of the platform screens are placed in the GUI module, which is not available for 7.0+ CUBA applications by default. In case the target application does not have the GUI module it has to be included into the application to place the translation files into it.

Adding the GUI module via CUBA Studio can be done via the Menu:

CUBA > Advanced > Manage Modules > Create 'gui' module.

Create Package-Mirror in the Application

After ensuring the target module is available in the application, the package where the original translation files exist, have to be created in the application as well. In this directory the additional / replacing message files are placed. CUBA will pick up the files and display the correct translations for the screen.

In the Petclinic example, the destination target package has been created for the Users browse screen: messages_de.properties to provide german translation for the screen.

The resulting Users browse screen in german locale looks like this:

localized platform screen

Summary

CUBA supports localization of applications out of the box. It provides a mechanism that allows application builders to provide different languages / locales for the application. Each user can pick and choose the language of choice.

A message key is used in the application code to reference a localized message, which will at runtime translated into the destination language. There are several ways to interact with the localization mechanism. The declarative XML based reference as well as the programmatic reference via the MessageBundle Interface was shown.

There are existing translations available in the platform itself and provided by the community, to also cover the translations of platform features and screens like the administration screens.