julienrf / play-jsmessages

Library to compute localized messages of your Play application on client side
MIT License
124 stars 32 forks source link
i18n javascript playframework

Play JsMessages library

This library allows you to compute localized messages on client-side, in Play projects.

Basically, play-jsmessages takes the i18n messages of your Play application, sends them to the client-side as a JSON object and defines a JavaScript function returning a message value from a given language and the message key and arguments.

Take a look at the Scala and Java samples to see it in action.

Installation (using sbt)

Add a dependency on the following artifact:

libraryDependencies += "org.julienrf" %% "play-jsmessages" % "7.0.0"

The current 7.0.0 version is compatible with Play 3.0 and Scala 2.13 and 3.3.

Previous versions are available here:

API Documentation

You can browse the online scaladoc.

Quick start

Select which messages you want to support on client-side

Get a JsMessagesFactory

The JsMessagesFactory class is the starting point: it allows you to select which messages of your application you want to support on client-side. You can use all the messages of the application or just a subset of them.

The simplest way to get a JsMessagesFactory is via dependency injection:

import jsmessages.JsMessagesFactory
import javax.inject.Inject

class Application @Inject() (jsMessagesFactory: JsMessagesFactory) extends Controller {

}

The equivalent Java code is the following:

import jsmessages.JsMessagesFactory;
import javax.inject.Inject;

public class Application extends Controller {

  @Inject
  public Application(JsMessagesFactory jsMessagesFactory) {

  }

}

The constructor of the JsMessagesFactory itself needs to be injected a play.api.i18n.MessagesApi parameter. By default, Play defines a binding that uses a play.api.i18n.DefaultMessagesApi, but you can disable this binding if you want to use something else.

For more control, you can also manually instantiate your JsMessagesFactory:

val jsMessagesFactory = new JsMessagesFactory(someMessagesApi)

The equivalent Java code is the following:

JsMessagesFactory jsMessagesFactory = new JsMessagesFactory(someMessagesApi);

Note that, in Scala, you may want to use the JsMessagesFactoryComponents trait that provides a jsMessagesFactory: JsMessagesFactory member using an abstract messagesApi: MessagesApi member.

Select which messages to use

You can either use all your i18n messages:

val jsMessages = jsMessagesFactory.all

Or, just an extensive subset:

val jsMessages = jsMessagesFactory.subset("error.required", "error.number")

Or, finally, an intensive subset:

val jsMessages = jsMessagesFactory.filtering(_.startsWith("error."))

The equivalent Java code of the above expressions are the following:

import jsmessages.JsMessages;
import play.libs.Scala;
JsMessages jsMessages = jsMessagesFactory.all();
JsMessages jsMessages = jsMessagesFactory.subset(Scala.varargs("error.required", "error.number"));
JsMessages jsMessages = jsMessagesFactory.filtering(new scala.runtime.AbstractFunction1<String, Boolean>() {
  @Override
  public Boolean apply(String key) {
    return key.startsWith("error.");
  }
});

Generate a JavaScript asset …

… for the client’s preferred language

Then, you typically want to define an action returning a JavaScript resource containing all the machinery to compute localized messages from client-side, using the client’s preferred lang:

val messages = Action { implicit request =>
  Ok(jsMessages(Some("window.Messages")))
}

Note that for this to work the apply method of jsMessages needs to have an implicit play.api.i18n.Messages value. You can get one by mixing the play.api.i18n.I18nSupport trait in your controller.

Or in Java:

@Inject
private MessagesApi messagesApi;

public Result messages(Http.Request request) {
    return ok(jsMessages.apply(Scala.Option("window.Messages"), this.messagesApi.preferred(request)));
}

The above code creates a Play action that returns a JavaScript program containing the localized messages of the application for the client language and defining a global function window.Messages. This function returns a localized message given its key and its arguments:

console.log(Messages('greeting', 'Julien')); // will print e.g. "Hello, Julien!" or "Bonjour Julien!"

The JavaScript function can also be supplied alternative keys that will be used if the main key is not defined. Keys will be tried in order until a defined key is found:

alert(Messages(['greeting', 'saluting'], 'Julien'));

The JavaScript function stores the messages map in a messages property that is publicly accessible so you can update the messages without reloading the page:

// Update a single message
Messages.messages['greeting'] = 'Hi there {0}!';
// Update all messages
Messages.messages = {
  'greeting': 'Hello, {0}!'
}

… for all the languages

Alternatively, you can use the all method to generate a JavaScript program containing all the messages of the application instead of just those of the client’s current language, making it possible to switch the language from client-side without reloading the page:

val messages = Action {
  Ok(jsMessages.all(Some("window.Messages")))
}

The equivalent Java code is the following:

public Result messages() {
    return ok(jsMessages.all(Scala.Option("window.Messages")));
}

In this case, the generated JavaScript function takes an additional parameter corresponding to the language to use:

console.log(Messages('en', 'greeting', 'Julien')); // "Hello, Julien!"
console.log(Messages('fr', 'greeting', 'Julien')); // "Bonjour Julien!"

Moreover, the messages property of the JavaScript function is a map of languages indexing maps of messages.

The JavaScript function can also be partially applied to fix its first parameter:

val messagesFr = Messages('fr'); // Use only the 'fr' messages
console.log(messagesFr('greeting', 'Julien')); // "Bonjour Julien!"

Note: if you pass undefined as the language parameter, it will use the default messages.

Changelog

License

This content is released under the MIT License.