Take advantage of Magnolia's internationalization (i18n) mechanism to make your app accessible to users in many languages and locales. Do not use hard coded message strings in your code. Instead, move all message keys and strings to a text file that translators and localizers can work with easily. 

Creating a message bundle

A message bundle (resource bundle in Java) is a collection of .properties files. Each file contains key-value pairs of translated user interface text such as labels and messages. The keys in all files of the same bundle are identical but the values are language specific translations. A message bundle must contain at least one .properties file. The files are named after the language (locale): <bundle-name>_<locale>.properties, for example app-pages-messages_en.propertiesEvery Magnolia module should provide its own message bundle. If a module installs several apps, each app should have its own message bundle.

To create a message bundle:

  1. Go to /src/main/resources/mgnl-i18n in the module source files. If the folder doesn't exist, create it.
  2. Add a new file app-<app-name>-messages_en.properties. This is the first file in the message bundle.

Your folder hierarchy should look like this:

 

Every app should have its own message bundle. So if your module deploys two apps, create two bundles.

Message file name

Magnolia will find your message files as long as they are in the mgnl-i18n folder, so the file name is not significant. However, we recommend that you follow this pattern:

app-<app-name>-messages_<locale>.properties

Examples:

  • app-security-messages_en.properties
  • app-contacts-messages_de.properties
  • app-products-messages_zh_CN.properties

(warning) Make sure the locale is correct. Magnolia follows the Java locale notation.

Location of message files

(warning) Magnolia 5.1+: Add message files in <module>/src/main/resources/mgnl-i18n/. The files will be loaded automatically during module startup. If your project uses message bundles from other locations there is no need to move their files – the old i18n mechanism still works. However, if you create a new message bundle on Magnolia 5.1+ we recommend you add the files in the mgnl-i18n directory.

The old i18n mechanism is still used in templating. Templates do not use the updated i18n features. See  MAGNOLIA-5876 - Getting issue details... STATUS .

Adding keys

Add keys to your message file. Here is an example pattern. Copy the text to the .properties file and replace the placeholders with the names your apps, subapps, actions, form fields and messages.

app-<app-name>-messages_en.properties
# App name and icon
<app-name>.app.label=
<app-name>.app.icon=

# Action bar sections
<app-name>.<subapp-name>.actionbar.sections.<section-name>.label=
<app-name>.browser.actionbar.sections.root.label=
<app-name>.browser.actionbar.sections.folder.label=Folder
<app-name>.browser.actionbar.sections.preview.label=Preview

# Actions
<app-name>.<subapp-name>.actions.<action-name>.label=
<app-name>.browser.actions.addFolder.label=
<app-name>.browser.actions.editFolder.label=
<app-name>.browser.actions.deleteFolder.label=
<app-name>.detail.actions.cancel.label=
<app-name>.detail.actions.save.label=

# Editor 
<app-name>.label=
<app-name>.description=

# Forms
<app-name>.<tab-name>.label=
<app-name>.<tab-name>.<field-name>.label=
<app-name>.<tab-name>.<field-name>.description=

# Dialogs
<app-name>.<dialog-name>.label=
<app-name>.<dialog-name>.description=

# Message views
<app-name>.<message-view-name>.actionbar.sections.<section-name>.label=
<app-name>.<message-view-name>.actions.<action-name>.label=
<app-name>.<message-view-name>.actions.<action-name>.successMessage=
<app-name>.<message-view-name>.actions.<action-name>.errorMessage=
<app-name>.<message-view-name>.actions.<action-name>.failureMessage=
 
# Templates
<module-name>.pages.<page-name>.title=
<module-name>.pages.<page-name>.description=
<module-name>.areas.<area-name>.title=
<module-name>.areas.<area-name>.description=
<module-name>.components.<component-name>.title=
<module-name>.components.<component-name>.description=

Unique key names

Keys in message files in the mgnl-i18n/ directory must be unique throughout all files that directory. If you follow the pattern shown above your keys will be unique.

Finding a key

Can't find a message key? Try logging the info.magnolia.i18nsystem.TranslationServiceImpl class while you use your app. This class prints keys into the catalina.out log file as Magnolia looks for them in bundles.

  1. In the Logging tool, set the logging level for TranslationServiceImpl to DEBUG.
  2. Use your app. Open all subapps and dialogs.
  3. In the log, look for messages starting with Looking up in global i18n message bundle with key.
catalina.out raw output
2014-04-04 13:10:45,604 DEBUG info.magnolia.i18nsystem.TranslationServiceImpl   : Looking up in global i18n message bundle with key [[sample.app.label, sample.app]] and Locale [de]
2014-04-04 13:10:45,605 DEBUG info.magnolia.i18nsystem.TranslationServiceImpl   : Looking up in global i18n message bundle with key [[sample.app.icon]] and Locale [de]
2014-04-04 13:10:45,605 DEBUG info.magnolia.i18nsystem.TranslationServiceImpl   : Looking up in global i18n message bundle with key [[app-launcher.dev.label, app-launcher.dev, app-launcher.dev]] and Locale [de]
2014-04-04 13:10:45,605 DEBUG info.magnolia.i18nsystem.TranslationServiceImpl   : Looking up in global i18n message bundle with key [[products.app.icon]] and Locale [de]
2014-04-04 13:10:45,612 DEBUG info.magnolia.i18nsystem.TranslationServiceImpl   : Looking up in global i18n message bundle with key [[pages.app.label, pages.app]] and Locale [de]
...

The raw log output can be confusing since it also contains keys from other Magnolia apps. Parse the log to find keys that are relevant to your app only. Here's an example command you can run in an OS X Terminal:

cat catalina.out | egrep -o "products(\.\w+)+" | sort --unique

Where:

  • cat catalina.out prints the contents of the log file to the console
  • egrep -o "products(\.\w+)+"  matches message keys that start with your app name. Replace products with your app name.
  • sort –unique ignores duplicate entries
catalina.out parsed output
products.app
products.app.icon
products.app.label
products.browser
products.browser.actionbar.sections.folder
products.browser.actionbar.sections.folder.label
products.browser.actionbar.sections.product
products.browser.actionbar.sections.product.label
...

Fallback to Magnolia keys

At startup, Magnolia creates a global message bundle by reading keys from all .properties files in mgnl-i18n folders of all modules in the current project. The global bundle is a virtual bundle, not a physical file. It includes keys from your module and keys from Magnolia's own modules such as ui-admincentral and ui-framework.

This means that you don't need to provide keys for all UI elements in your app. Keys for commonly reused elements such as buttons and subapps are in Magnolia's own modules. Just reuse the UI element in your app and Magnolia will find the existing key automatically.

Example: The contact edit form has two buttons: Save Changes and Cancel. The Contacts app doesn't provide keys for either button in its own message bundle. The keys are provided in module-ui-framework-messages_en.properties.

module-ui-framework-messages_en.properties
# Default Action Labels
actions.commit=save changes
actions.cancel=cancel

If your app needs these buttons, just name your dialog actions commit and cancel.

Node nameValue

 actions

 

 commit

 

 class

info.magnolia.ui.form.action.SaveFormActionDefinition

 implementationClass

info.magnolia.contacts.app.form.action.SaveContactFormAction

 cancel

 

 class

info.magnolia.ui.form.action.CancelFormActionDefinition

Using the translation service

Use the  SimpleTranslator  translation service when you create UI elements in code. It is a good practice to make all user interface text translatable. In a simple content app, you likely won't need to create UI elements in code since most can be configured. But if you do a custom app or anything more complex, this is how to use the service.

Java code

(warning) Do NOT instantiate SimpleTranslator but inject it in the constructor. 

import info.magnolia.i18nsystem.SimpleTranslator;
import com.vaadin.ui.Button;
import javax.inject.Inject;

public class MyClass {

    private final SimpleTranslator i18n;
	
	@Inject
    public MyClass(SimpleTranslator i18n){
        this.i18n = i18n;
    }

    public void someMethod(){
        // more code here ...
        Button sendMessageButton = new Button(i18n.translate("messages-app.app.button.sendMessage"));
        // more code here ...
    }
}

Then pass the key in the #translate(String key).

The key messages-app.app.button.sendMessage must be in a message file with a value:

myapp-messages_en.properties
messages-app.app.button.sendMessage=Send message

Examples:

Template script

Similar to Java code, you can use the translation service in a template script.

Freemarker
${i18n['link.readon']}
JSP
<fmt:message key="link.readon"/>

Decorated classes and autogenerated keys

The i18n API generates keys for some objects automatically. This means you don't need to add i18nBasename, label and description properties. The new API is practical especially in dialogs which usually have a lot of translatable text.

The following logic generates automatic keys. 

If an object is able to autogenerate its keys, its class or superclass or usually its interface is annotated with  I18nable . It means, these objects are internationalized. When annotating classes with I18nable, you must provide the responsible Keygenerator class which implements  I18nKeyGenerator .

Internationalized classes also have methods annotated with  I18nText . Every annotated method leads to an autogenerated key. Depending on the Keygenerator, the generator may generate more than one key for the same method.

Example:  DialogDefinition

info.magnolia.ui.dialog.definition.DialogDefinition
/**
 * Base definition of a dialog.
 */
@I18nable(keyGenerator = DialogDefinitionKeyGenerator.class)
public interface DialogDefinition {
    String getId();

    @I18nText
    String getLabel();

    String getI18nBasename();

    Map<String, ActionDefinition> getActions();

    Class<? extends DialogPresenter> getPresenterClass();

    EditorActionAreaDefinition getActionArea();

    ModalityLevel getModalityLevel();

    boolean isWide();
}

Note the annotations on lines 4 and 8.

When objects of internationalized classes are instantiated, a proxy will generate a decorated object which knows its keys and which is able to fetch the translations for these keys according to the context.

 

#trackbackRdf ($trackbackUtils.getContentIdentifier($page) $page.title $trackbackUtils.getPingUrl($page))