e(fx)clipse 1.1 – New features – Improved internationalization support


e(fx)clipse 1.1.0 is released in less than a week so I’ll once more go through the enhancements and features we developed in the 1.1 timeframe.

Improved internationalization support

As part of this release we’ve added a first set of runtime APIs makeing it easier to develop localized applications. The following APIs are now available to you:

  • AbstractTextRegistry: allows you to connect localization receivers (most likely your UI-Control) and the localization provider so that they update automatically when flipping the language
  • Formatter: a set of formatters for numbers, java.util.Date and java.time.TemporalAccessor who automatically update to the current application locale
  • MessageFormatter: Allows you to do similar things to java.text.MessageFormat but is a bit more powerful 😉

AbstractTextRegistry

Let’s look at first into AbstractTextRegistry and its usage – I won’t go in the technical details how we reached at this API because this has already been discussed in another blog post.

The first thing you need is a class holding your translation texts:

public class MyMessages {
   public String mySimpleMessage;
}

And a properties file named MyMessages.properties (and for each language you want to support another one e.g. MyMessages_de.properties, …)

mySimpleMessage = This is a simple message

And another class which is a subclass of AbstractTextRegistry

@Creatable
public class MyMessagesRegistry extends AbstractTextRegistry<MyMessages> {
  public String mySimpleMessage() {
     return getMessages().mySimpleMessage;
  }

  @Inject
  public void updateMessages(MyMessages messages) {
    super.updateMessages(messages);
  }
}

And then the final useage in your UI is as simple as:

public class MyUIPart {
   @Inject
   MyMessagesRegistry r;

   @PostConstruct
   public void createUI(BorderPane p) {
     Label l = new Label();
     r.register(l::setText, r::mySimpleMessage);
   }
}

Formatter

Another thing you often need in your application are formatters e.g. to format numbers, dates, … so we’ve create a basic interface

public class Formatter<T> {
  public String format(T object, String format);
}

and added some useful basic implementation DateFormatter, NumberFormatter and TemporalAccessorFormatter and you can get access to them through dependency injection

public class MyUIPart {
   @Inject
   NumberFormatter numberFormatter;

   @PostConstruct
   public void createUI(BorderPane p) {
     Label l = new Label();
     l.setText(numberFormatter.format(20_000, "#,##0.00"));
   }
}

MessageFormatter

While the above formatters allow you to format single objects like dates, numbers, … this one allows you format messages similar to what you are used from java.text.MessageFormat but there are some difference who make it a lot for powerful.

public class MyUIPart {
   @Inject
   NumberFormatter numberFormatter;

   @PostConstruct
   public void createUI(BorderPane p) {
     Label l = new Label();
     String message = "The final amount is ${amount,number,#,##0.00}";
     
     Map<String,Object> data = 
       Collections.singletonMap("amount", 20_000);
     Map<String,Formatter<?>> formatters = 
       Collections.singletonMap("number",numberFormatter);

     l.setText(
       MessageFormatter.create( 
         data::get, formatters::get ).apply(message) );
   }
}

So you notice we are not using indices like in MessageFormat but a key and the other difference is that you are free to add formatters as you need them to do more complex things.

Let the 3 APIs work together for the common good

So while the APIs alone already provide some benefits over the lower level JDK APIs their real power can be seen when you let them work together.

If we come back to our initial AbstractTextRegistry stuff we often have stored in our translation texts something like this:

# ...
myAmountMessage = The final amount is ${amount,number,#,##0.00}

which results in another field in MyMessages

public class MyMessages {
  // ...
  public String myAmountMessage;
}

and 3! more methods in MyMessagesRegistry

@Creatable
public class MyMessagesRegistry {
  // ...
  public String myAmountMessage() {
    return getMessages().myAmountMessage;
  }

  @Inject
  NumberFormatter numberFormatter;

  public String myAmountMessage(Number amount) {
     Map<String,Object> data = 
       Collections.singletonMap("amount", amount);
     Map<String,Formatter<?>> formatters = 
       Collections.singletonMap("number",numberFormatter);
     return MessageFormatter.create( 
         data::get, formatters::get ).apply(myAmountMessage());
  }

  public Supplier<String> myAmountMessage_supplier(Number amount) {
    return () -> myAmountMessage(amount);
  }
}

which result in your UI code looking like this

public class MyUIPart {
   @Inject
   MyMessagesRegistry r;
   
   @PostConstruct
   public void createUI(BorderPane p) {
     Label l = new Label();
     r.register(l::setText, r.myAmountMessage_supplier(20_000));
   }
}

That’s it for the first runtime feature, the next blog post will show you how you can make Eclipse generate all the localization artifacts for you from a single resource instead of writing them by hand.

Advertisement
This entry was posted in e(fx)clipse and tagged . Bookmark the permalink.

6 Responses to e(fx)clipse 1.1 – New features – Improved internationalization support

  1. WhoCares says:

    Hi Tom,

    looks like a nice idea and many thanks for the great JavaFX tooling.

    Regards
    WhoCares

    P.S.: A small Copy&Paste error in the last example I guess:

    public String myAmountMessage(Number amount) {
    Map data =
    Collections.singletonMap(“amount”, 20_000);
    Map<String,Formatter> formatters =
    Collections.singletonMap(“number”,numberFormatter);
    return MessageFormatter.create(
    data::get, formatters::get ).apply(myAmountMessage());
    }

    would work better if it is changed to:

    public String myAmountMessage(Number amount) {
    Map data =

    Collections.singletonMap(“amount”, amount); // <<< to have a dynamic value.

    Map<String,Formatter> formatters =
    Collections.singletonMap(“number”,numberFormatter);
    return MessageFormatter.create(
    data::get, formatters::get ).apply(myAmountMessage());
    }

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

  3. Pingback: e(fx)clipse 1.1.0 released | Tomsondev Blog

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.