Fork me on GitHub

Internationalization

Note: All files of Ninja are encoded in UTF-8. Yes. Also the .properties files. If you edit the files make sure your editor actually uses UTF-8 and not the default ISO encoding!

Defining your supported languages

First you need a definition of supported languages in your conf/application.conf file. This is a simple comma separated list (whitespaces are omitted).

application.languages=en,de

The languages are one or two part ISO coded languages. Usually they resemble language or language and country. Examples are “en”, “de”, “en-US”, “en-CA” and so on.

Defining messages for your application

The message file name follows a convention. The convention is messages_LANGUAGE.property or messages_LANGUAGE-COUNTRY.property.

Some examples:

  • language “en” is specified in conf/messages_en.properties
  • language “en-US” is specified in conf/messages_en-US.properties

conf/messages_en.properties might look like:

# registration.ftl.html
casinoRegistrationTitle=Register
casinoRegistrationEmail=Your email
casinoRegistrationConfirm=Confirm
casinoRegistrationAcceptTermsOfService=Accept terms of service          
casinoRegistrationRegister=Register
casinoRegistrationFlashError=An error occurred.

casinoYourUsername=Your username is: {0}

# registrationPending.ftl.html
registrationPleaseVerifyEmailAddress=Please check your email inbox to verify your account.
registrationPendingError=Error confirming email.
registrationPendingSuccess=Success confirming email.  

Internally we use MessageFormat.format(text, values) to format the messages. Therefore all the information from https://docs.oracle.com/javase/7/docs/api/java/text/MessageFormat.html does apply.

MessageFormat is really cool, but there is one thing to keep in mind: The apostrophe ' is a special character used for escaping. If you need ASCII apostrophes you have to enter them two times like ''.

Getting a message in your code

Ninja provides the message through the class Messages.

You can inject and use Messages in your application like so:

public class ApplicationController {

    Messages msg

    @Inject
    ApplicationController(Messages msg) {
        this.msg = msg
    }

    public Result controllerMethod(Context context) {

        Optional<String> language = Optional.of("en");
        Optional<Result> optResult = Optional.absent();

        // messages use messageFormat. If you use placeholders, messages can format them for you.
        Object [] messageParamters = {"kevin"};

       String message1 = "localized message1: " + msg.get("casinoRegistrationTitle", language);

       // This will determine the language from context and result:
       String message2 = "localized message2: " + msg.get("casinoYourUsername", context, optResult, messageParamters);

       return Results.text(message1 + " " + message2);

    }

}

Getting a message inside a template

Inside a Freemarker template (ftl.html) you can get internationalized messages by using

<html>
    <head>
        <title>${i18n("casinoRegistrationTitle")}</title>
    </head>
<html>

You can also format messages automatically:

<html>
    <head>
        <title>${i18n("casinoYourUsername", username)}</title>
    </head>
<html>
If a value for a requested key is missing you'll get the key inside the rendered template as value. For instance ${i18n("my.message.key")} will be displayed as "my.message.key" inside the template if your messages.properties file is missing that key.

Fallback messages

You can define fallback messages in the file message.properties.

Ninja always looks up messages from more specific to less specific.

Example: The user requests a message in “en-US”. The lookup then is

  • return messages_en-US.properties if file and key is found or
  • return messages_en.properties if file and key is found or
  • return messages.properties if file and key is found or
  • return null

If you specify

application.languages=en

It makes sense to only have one message file called messages.properties in English. Therefore English acts as fallback for all languages - country combinations.

Setting a language by force

Ninja tries to do its best to determine the language from the Accept-Language header. But there are times, when it makes sense to ignore the header and force the usage of a certain language.

Ninja provides that possibility by a cookie. The cookie is usually called NINJA_LANG and contains only one value - the language to use for this user.

You can set the language by using the Lang tools like so:

@Inject
Lang lang;

public Result index() {

    Result result = Results.html().ok();
    lang.setLanguage("de", result);

    return result;

}

After setting the language all messages will be displayed in German.

Flash scope and i18n translation

The flash scope is available in the template via e.g. ${flash.error}. There is a simple rule regarding i18n: If the value of the flash scope key (eg “error”) can be found in the messages the translated version is used. Otherwise the value is used without any translation.

Consider the messages file introduced some sections above. If you'd use casinoRegistrationFlashError as error in your flash cookie it would be automatically translated into “An error occurred”. Using “An error occurred - please check your input” as value won't trigger any translation as the value cannot be found.

One note: This automatic translation facility cannot be used when placeholders aka {0} are used. In that case you have to translate the message in your controller and set the translated value yourself (See the demo application for more hints).

Translating your messages with placeholders inside your controller would look like:

public Result flashError(Context context) {

    Result result = Results.html();

    Optional<String> flashMessage = messages.get("flashError", context, Optional.of(result), "PLACEHOLDER");

    if (flashMessage.isPresent()) {
        context.getFlashScope().error(flashMessage.get());
    }

    return result;

}