Effeciently dealing with @Translations in an e4 app with the help of Java8


In my JavaOne application i showed how one can flip the language of a running e4+JavaFX application on the fly using the new support available from the e4 framework using the @Translation-Annotation.

Generally speaking the advised way of doing translations is like this in case you don’t need to support language fliping on the fly:

  • Create a Messages-Java-Class like this
    public class Messages {
      public String MyLabel;
    }
    
  • Get access to the translations like this:

    public class MyUI {
      @Inject
      @Translation
      Messages messages;
    
      @PostConstruct
      void init(BorderPane parent) {
        Label l = new Label();
        l.setText(messages.MyLabel);
      }
    }
    

In case you want your application to support dynamic language flipping (take care a language flip does not only affect labels, you also need to update formats, …) the situation gets a bit more complex.

public class MyUI {
  private Messages messages;

  private Label l;

  @Inject
  public void applyMessages(@Translation Messages messages) {
    this.messages = messages;
    if( l != null ) {
      l.setText(messages.MyLabel);
    }
  }

  @PostConstruct
  void init(BorderPane parent) {
    l = new Label();

    applyMessages(messages);
  }
}

If you have many labels fields you see that you create a big amount of fields you remember because of the language switching.

If you start analyzing the situation you realize that you only remember the Label instance to call the setText(String) method or more generically speaking something that follows the Java8 interface Consumer<String> so you could write in Java8

public class MyUI {
  private Messages messages;

  private Consumer<String> text;

  @Inject
  public void applyMessages(@Translation Messages messages) {
    this.messages = messages;
    if( text != null ) {
      text.accept(messages.MyLabel);
    }
  }

  @PostConstruct
  void init(BorderPane parent) {
    Label l = new Label();
    text = l::setText

    applyMessages(messages);
  }
}

The situation for the String extraction fairly similar instead of directly access it you could abstract this using a Supplier<String> from Java8.

public class MyUI {
  private Messages messages;

  private Consumer<String> text;
  private Supplier<String> textProvider;

  @Inject
  public void applyMessages(@Translation Messages messages) {
    this.messages = messages;
    if( text != null ) {
      text.accept(textProvider.get());
    }
  }

  @PostConstruct
  void init(BorderPane parent) {
    Label l = new Label();
    text = l::setText
    textProvider = () -> messages.MyLabel;

    applyMessages(messages);
  }
}

You might say: Well now you have 2 fields instead of 1 so what’s the point? The point is the applyMessages only knows about Consumer & Supplier, the method and fields access has been abstracted away and so we can make it generic.

public class MyUI {
  private Messages messages;

  private Map<Consumer<String>, Supplier<String>> translations = new HashMap<>();

  @Inject
  public void applyMessages(@Translation Messages messages) {
    this.messages = messages;
    for( Entry<Consumer<String>, Supplier<String>> e : translations.entrySet() ) {
      e.getKey().accept(e.getValue().get());
    }
  }

  private void register(Consumer<String> c, Supplier<String> s) {
    c.accept(s.get());
    translations.put(c,s);
  }

  @PostConstruct
  void init(BorderPane parent) {
    Label l = new Label();
    register(l::setText, () -> messages.MyLabel);
  }
}

This is already very good but writing this all time is also a bit tedious, right? That’s why e(fx)clipse core-runtime (which means even if you are using e4+SWT you can make use of it!) provides you a base class which uses exactly that pattern. So this is the final solution which remove all the translation flipping clutter from your view code.

  • Create a class named MessageRegistry like this:
    @Creatable
    public class MessageRegistry<Messages> extends org.eclipse.fx.core.di.AbstractMessageRegistry {
        @Inject
        public void updateMessages(@Translation Messages messages) {
            super.updateMessages(messages);
        }
     
        // Optional provide method to access current message
        public String MyLabel() {
            return getMessages().MyLabel;
        }
    }
    
  • Use it in your view like this:

    @Inject
    MessageRegistry r;
     
    @PostConstruct
    void init(BorderPane parent) {
        Label l = new Label();
     
        // if you opted to NOT add the MyLabel() method
        r.register(l::setText, (m) -> m.MyLabel); 
     
        // if you opted to have a MyLabel() method
        r.register(l::setText, r::MyLabel);
    }
    
Advertisement
This entry was posted in e(fx)clipse, e4. Bookmark the permalink.

6 Responses to Effeciently dealing with @Translations in an e4 app with the help of Java8

  1. Pingback: Busy JavaFX Autumn a head – EclipseCon Europe & Democamps & e(fx)clipse 1.1.0 | Tomsondev Blog

  2. Pingback: e(fx)clipse 1.1 – New features – Improved internationalization support | Tomsondev Blog

  3. Awesome tutorial, thank you!
    One question though: how would one go about testing the language switching feature (on the fly without app restart)?

  4. kursat says:

    Great tutorial. Can I use this in not-e4 application. Pure osgi and Fx?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.