{#========================================== Docs : "Miscellaneous" ==========================================#}

Miscellaneous

{#========================================== Dictionary ==========================================#}

Dictionary (i18n / internationalization)

Usage

The Dictionary interface represents the object in which you store and from which you get localized messages for a multilingual application.

You get a localized message by injecting the Dictionary in a class and by specifying the key of the message to get :

public class MyClass { 

    private final Dictionary dictionary;
    
    @Inject
    public MyClass(Dictionary dictionary) {
        this.dictionary = dictionary;
    }
    
    protected Dictionary getDictionary() {
        return this.dictionary;
    }
    
    public void someMethod() {
        String localizedMessage = getDictionary().get("some.message.key");
        System.out.println(localizedMessage);
    }
}

In this example, the message key is "some.message.key"

By default, the Locale used to pick the right version of the message is the one returned by the Locale Resolver. But you can also specify the Locale as a parameter:


String localizedMessage = getDictionary().get("some.message.key", Locale.JAPANESE);
System.out.println(localizedMessage);

The default Dictionary implementation, SpincastDictionaryDefault, uses the Templating Engine to evaluate messages. This means that you can pass parameters when getting a message. For example :

{% verbatim %}


// Let's say the "some.message.key" message in the dictionary is :
// "Hi {{userName}}! My name is {{authorName}}."

String localizedMessage = getDictionary().get("some.message.key",
                                              Pair.of("userName", user.name),
                                              Pair.of("authorName", admin.name));
{% endverbatim %}

Note that, to improve performance, a message from the dictionary is only evaluated using the Templating Engine if at least one parameter is passed when getting it! Otherwise, the message is going to be returned as is, without any evaluation. If you have a message that doesn't require any parameter but still needs to be evaluated, you can force the evaluation using the "forceEvaluation" parameter :


String localizedMessage = getDictionary().get("some.message.key", true);

Finally, note that Spincast also provides a msg(...) function to get a localized message from a template (a HTML template, for example).

Adding messages to the dictionary

By default, only some Spincast core messages and some plugins messages will be added to the Dictionary. To add your own messages, the ones required in your application, you extend the SpincastDictionaryDefault base class and you override the addMessages() method. By using the key() and msg() helpers, you then specify the localized messages of your application. For example :

{% verbatim %}


public class AppDictionary extends SpincastDictionaryDefault {

    @Inject
    public AppDictionary(LocaleResolver localeResolver, 
                         TemplatingEngine templatingEngine, 
                         AppConfigs appConfig,
                         Set<DictionaryEntries> dictionaryEntries) {
        super(localeResolver, templatingEngine, appConfig, dictionaryEntries);
    }

    @Override
    protected void addMessages() {

        key("users.home.welcome",
            msg("", "Hi {{name}}!"),
            msg("fr", "Salut {{name}}!"));

        key("users.profile.title",
            msg("", "Your profile"),
            msg("fr", "Votre profil"),
            msg("ja", "あなたのプロフィール"));
            
        // ...      
    }
}
{% endverbatim %}

The first parameter of the msg() helper is the language of the message. The empty language ("") is called the fallback language or default language. It is english in our example. If a message is requested using a specific Locale but is not found, Spincast will return the message using the fallback language, if it exists.

The default Dictionary implementation, SpincastDictionaryDefault, uses the Templating Engine to evaluate messages. This means that you can perform logic inside your messages ("if", "for loops"... anything supported by Pebble) and you can use parameters, as we can see with the {% verbatim %}"{{name}}"{% endverbatim %} part in the previous example.

Finally, don't forget to register your custom implementation of the Dictionary interface (in our example: "AppDictionary") in your Guice context! For example :

{% verbatim %}

public class AppModule extends SpincastGuiceModuleBase {

    @Override
    protected void configure() {

        bind(Dictionary.class).to(AppDictionary.class).in(Scopes.SINGLETON);
        
        //...
    }
}
{% endverbatim %}

Overriding core messages and plugins messages

Spincast core messages and plugins's messages are added to the Dictionary before you add your own messages using the addMessage() method. This means that you can override them and even translate them in a new language, if required.

The plugins should provide public constants representing the keys of the messages they use. This way, you can easily know how to override them. For example, the keys of Spincast's core messages are provided as constants in the SpincastCoreDictionaryEntriesDefault class. Each plugins should similarly list the keys of their messages in their respective documentation.

Here's an example of overriding a core Spincast message:

{% verbatim %}


public class AppDictionary extends SpincastDictionaryDefault {

    @Inject
    public AppDictionary(LocaleResolver localeResolver, 
                         TemplatingEngine templatingEngine, 
                         AppConfigs appConfig,
                         Set<DictionaryEntries> dictionaryEntries) {
        super(localeResolver, templatingEngine, appConfig, dictionaryEntries);
    }

    @Override
    protected void addMessages() {

        // Override a core message!
        key(SpincastCoreDictionaryEntriesDefault.MESSAGE_KEY_ROUTE_NOT_FOUND_DEFAULTMESSAGE,
            msg("", "my custom message!"));

        // Then, add your custom messages...
    }
}
{% endverbatim %}

Configuration

A configuration for the dictionary is available through SpincastConfig:

Note that if a version of the message exists with the fallback language (""), it will always be found.

{#========================================== Flash Messages ==========================================#}

Flash messages

A Flash message is a message that is displayed to the user only once. It is most of the time used to display a confirmation message to the user when a form is submitted and the page redirected.

A good practice on a website is indeed to redirect the user to a new page when a form has been POSTed and is valid : that way, even if the user refreshes the resulting page, the form won't be resubmitted. But this pattern leads to a question : how to display a confirmation message on the page the user is redirected to? Flash messages are the answer to this question.

(All the Forms & Validation demos use Flash messages to display a confirmation message when the Form is submitted and is valid... Try them!)

A Flash message is most of the time used to display a confirmation (success) message to the user, but it, in fact, supports three "levels" :

A Flash message can also have some variables associated with it (in the form of an JsonObject), and those variables can be used when the Flash message is retrieved.

You can specify a Flash message :

Of course, it doesn't make sense to specify a Flash message when redirecting the user to an external website!

Flash messages, when retrieved, are automatically added as a global variable for the Templating Engine. It is, in fact, converted to an Alert message.

How are Flash messages actually implemented? If Spincast has validated that the user supports cookies, it uses one to store the "id" of the Flash message and to retrieve it on the page the user is redirected to. If cookies are disabled, or if their support has not been validated yet, the "id" of the Flash messageis added as a queryString parameter to the URL of the page the user is redirected to.

{#========================================== Alert Messages ==========================================#}

Alert messages

{% if alertDemoMsg is not empty | default(false) %}

{{alertDemoMsg}}

{% endif %}

An Alert message is a message that has a Success, Warning or Error level and that is displayed to the user, usually at the top of the page or of a section. It aims to inform the user about the result of an action, for example.

There are multiple ways to display an Alert message on a website. The Spincast website uses Toastr.js when javascript is enabled , and a plain <div> when javascript is disabled ( Try it!)

An Alert message is simply some text and a level associated with it, both added as a templating variable. You can easily implement your own way to pass such messages to be displayed to the user, but Spincast suggests a convention and some utilities :

Note that Flash messages will be automatically converted to Alert messages when it's time to render a template! This means that as long as you add code to display Alert messages in your interface, Flash messages will also be displayed properly.

{#========================================== SSL ==========================================#}

Using a SSL certificate (HTTPS)

It is recommended that you serve your application over HTTPS and not HTTP, which is not secure. To achieve that, you need to install a SSL certificate.

If you download the Quick Start application, you will find two files explaining the required procedure :

{#========================================== Spincast Utilities ==========================================#}

Spincast Utilities

Spincast provides some generic utilities, accessible via the SpincastUtils interface :

{#========================================== @MainArgs ==========================================#}

@MainArgs

The init(...) method of the Bootstrapper allows you to bind the arguments received in your main(...) method. For example :


public static void main(String[] args) {

    Spincast.configure()
            .module(new AppModule())
            .init(args);
    //....
}

By doing so, those arguments will be available for injection, using the @MainArgs annotation :

public class AppConfig extends SpincastConfig implements AppConfig {

    private final String[] mainArgs;

    @Inject
    public AppConfig(@MainArgs String[] mainArgs) {
        this.mainArgs = mainArgs;
    }

    protected String[] getMainArgs() {
        return this.mainArgs;
    }

    @Override
    public int getHttpServerPort() {

        int port = super.getHttpServerPort();
        if(getMainArgs().length > 0) {
            port = Integer.parseInt(getMainArgs()[0]);
        }
        return port;
    }
}

{#========================================== Using an init() method ==========================================#}

Using an init() method

This is more about standard Guice development than about Spincast, but we feel it's a useful thing to know.

Guice doesn't provide support for a @PostConstruct annotation out of the box. And since it is often seen as a bad practice to do too much work directly in a constructor, what we want is an init() method to be called once the object it fully constructed, and do the initialization work there.

The trick is that Guice calls any @Inject annotated methods once the object is created, so let's use this to our advantage :

public class UserService implements UserService {

    private final SpincastConfig spincastConfig;

    @Inject
    public UserService(SpincastConfig spincastConfig) {
        this.spincastConfig = spincastConfig;
    }

    @Inject
    protected void init() {
        doSomeValidation();
        doSomeInitialization();
    }

    //...
}

Explanation :

What we recommend is constructor injection + one (and only one) @Inject annotated method. The problem with multiple @Inject annotated methods (other than constructors) is that it's hard to know in which order they will be called.

Finally, if the init() method must be called as soon as the application starts, make sure you bind the object using asEagerSingleton()!

{#========================================== ==========================================#}

Server started listeners

Spincast provides a hook so you can be informed when your application's HTTP server has been successfully started. For a class to be called:

1. The class must implement the ServerStartedListener interface. When the server is started, the serverStartedSuccessfully() method will be called:

public class MyClass implements ServerStartedListener { 

    @Override
    public void serverStartedSuccessfully() {
        System.out.println("Server started hook!");
    }
    
    //...
}

Note that each listener is called in a new Thread.

2. The class must be registered on the Multibinder<ServerStartedListener> multibinder, in your application's Guice module:

Multibinder<ServerStartedListener> serverStartedListenersMultibinder =
        Multibinder.newSetBinder(binder(), ServerStartedListener.class);
serverStartedListenersMultibinder.addBinding().to(MyClass.class).in(Scopes.SINGLETON);