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); }
Pingback: Busy JavaFX Autumn a head – EclipseCon Europe & Democamps & e(fx)clipse 1.1.0 | Tomsondev Blog
Pingback: e(fx)clipse 1.1 – New features – Improved internationalization support | Tomsondev Blog
Awesome tutorial, thank you!
One question though: how would one go about testing the language switching feature (on the fly without app restart)?
ILocaleChangeService#changeApplicationLocale
Great tutorial. Can I use this in not-e4 application. Pure osgi and Fx?
well yes but you need to DI-Framework