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
andjava.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.
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());
}
Completely correct –Â fixed
Fixed after a minute, that’s a real “QuickFix” 😉
BTW, will this new version also include the “@font-face” in CSS?
Of course 😉
Pingback: e(fx)clipse 1.1 – New features – Improved internationalization support – tooling | Tomsondev Blog
Pingback: e(fx)clipse 1.1.0 released | Tomsondev Blog