In the last blog I showed the runtime components we added to make it easier for you to localize your applications. In the following blog I’ll show you the tooling support we added.
Improved internationalization support – tooling
I’ve already stated in other posts that one of the strengths of e(fx)clipse is that tooling and runtime are developed side by side while you can use all runtime stuff without the Eclipse IDE using Eclipse as the development environment makes your life much easier.
If we recap the runtime blog post our suggested set of files needed are:
- One class file to hold the translation values (
MyMessages
)public class MyMessages { public String mySimpleMessage; }
- One class file giving you access to translation methods so that you can use method references or create suppliers (
MyMessagesRegistry
) – at “worst” you need to write 3 methods per property@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); } }
- 1 – n Property files e.g. for every language you want to support
# ... myAmountMessage = The final amount is ${amount,number,#,##0.00}
“Quite a bit of work to write and keep up to date” i hear you saying and you are right! So we need to improve, right? In an ideal world we’d have 1 file holding all the informations and generate all files from it!
So we need some special language which allows us to collect all information in one file – or put differently a DSL!
which generates
package org.eclipse.fx.testcases.l10n.app; /* * Do not modify - Auto generated from Message.l10n */ public class SamplePartMessages { public String SimpleText; public String SimpleDateText; public String SimpleNumberText; public String CustFormatText; }
package org.eclipse.fx.testcases.l10n.app; /* * Do not modify - Auto generated from Message.l10n */ @org.eclipse.e4.core.di.annotations.Creatable public class SamplePartMessagesRegistry extends org.eclipse.fx.core.text.AbstractTextRegistry<SamplePartMessages> { @javax.inject.Inject private org.eclipse.fx.core.text.NumberFormatter _number; @javax.inject.Inject private org.eclipse.fx.core.text.DateFormatter _date; @javax.inject.Inject private org.eclipse.fx.testcases.l10n.app.MyFormatter cust_myformatter; @javax.inject.Inject public void updateMessages(@org.eclipse.e4.core.services.nls.Translation SamplePartMessages messages) { super.updateMessages(messages); } public String SimpleText() { return getMessages().SimpleText; } public String SimpleDateText() { return getMessages().SimpleDateText; } public String SimpleDateText(java.util.Date now) { java.util.Map<String,Object> dataMap = new java.util.HashMap<>(); dataMap.put("now",now); java.util.Map<String,org.eclipse.fx.core.text.Formatter<?>> formatterMap = new java.util.HashMap<>(); formatterMap.put("-date",_date); return org.eclipse.fx.core.text.MessageFormatter.create(dataMap::get,formatterMap::get).apply( SimpleDateText() ); } public java.util.function.Supplier<String> SimpleDateText_supplier(java.util.Date now) { return () -> SimpleDateText(now); } public String SimpleNumberText() { return getMessages().SimpleNumberText; } public String SimpleNumberText(Number val) { java.util.Map<String,Object> dataMap = new java.util.HashMap<>(); dataMap.put("val",val); java.util.Map<String,org.eclipse.fx.core.text.Formatter<?>> formatterMap = new java.util.HashMap<>(); formatterMap.put("-number",_number); return org.eclipse.fx.core.text.MessageFormatter.create(dataMap::get,formatterMap::get).apply( SimpleNumberText() ); } public java.util.function.Supplier<String> SimpleNumberText_supplier(Number val) { return () -> SimpleNumberText(val); } public String CustFormatText() { return getMessages().CustFormatText; } public String CustFormatText(Number val) { java.util.Map<String,Object> dataMap = new java.util.HashMap<>(); dataMap.put("val",val); java.util.Map<String,org.eclipse.fx.core.text.Formatter<?>> formatterMap = new java.util.HashMap<>(); formatterMap.put("myformatter",cust_myformatter); return org.eclipse.fx.core.text.MessageFormatter.create(dataMap::get,formatterMap::get).apply( CustFormatText() ); } public java.util.function.Supplier<String> CustFormatText_supplier(Number val) { return () -> CustFormatText(val); } }
and SamplePartMessages.properties
# # Do not modify - Auto generated from Message.l10n # SimpleText = Hello World SimpleDateText = Hello World on ${now,-date,MMM/dd/yyyy} SimpleNumberText = Hello World ${val,-number,#,##0} times CustFormatText = Hello World ${val,myformatter,#,##0} times
and SamplePartMessages_de.properties
# # Do not modify - Auto generated from Message.l10n # SimpleText = Hallo Welt SimpleDateText = Hallo Welt am ${now,-date,dd.MM.yyyy} SimpleNumberText = Hallo Welt ${val,-number,#,##0} mal CustFormatText = Hallo Welt ${val,myformatter,#,##0} mal
Before wrapping up let me close with a feature I often thought about myself when doing localizations. One often wants to share general strings in the UI like yes, no, … and translate them only once (see the BasicMessage-Bundle definition) which would require you to use something like this in your UI
public class MyPart { @Inject MyMessagesRegistry messageRegistry; @Inject BasicMessagesRegistry basicRegistry; @PostConstruct void init(BorderPane p) { Label l = new Label(); messageRegistry.register(...); Button b = new Button(); basicRegistry.register(...); } }
What an overhead 2 registries, … wouldn’t it be much nicer if we could reuse messages from the Basic-Bundle in the My-Bundle? So have I been thinking as well so you can write something like this in your l10n-DSL-File
which leads to the following MyMessagesRegistry
package org.eclipse.fx.testcases.l10n.app; /* * Do not modify - Auto generated from Message.l10n */ @org.eclipse.e4.core.di.annotations.Creatable public class SamplePartMessagesRegistry extends org.eclipse.fx.core.text.AbstractTextRegistry<SamplePartMessages> { // ... @javax.inject.Inject private org.eclipse.fx.testcases.l10n.app.BasicMessagesRegistry bundle_BasicMessages; // ... public String ReferenceText() { return bundle_BasicMessages.BasicYes(); } public String ReferenceDyn() { return bundle_BasicMessages.BasicDyn(); } public String ReferenceDyn(Object val) { return bundle_BasicMessages.BasicDyn(val); } public java.util.function.Supplier<String> ReferenceDyn_supplier(Object val) { return () -> ReferenceDyn(val); } }
Hi,
another cool idea Tom.
Regards
WhoCares
P.S.: You should update the code examples again because during copy&paste all the , ” characters have been transformed to <, > and "
Hi Tom,
another QuickFix, looks much better now. But now I’ve got the question what does the minus infront of the formatter properties mean?
i.e.:
public String SimpleDateText(java.util.Date now) {
java.util.Map dataMap = new java.util.HashMap();
dataMap.put(“now”,now);
java.util.Map<String,org.eclipse.fx.core.text.Formatter> formatterMap = new java.util.HashMap();
formatterMap.put(“-date”,_date); // <<< what does this "-date", same for the number? does it have any special meaning or is it just a name?
return org.eclipse.fx.core.text.MessageFormatter.create(dataMap::get,formatterMap::get).apply( SimpleDateText() );
}
well if you look at the language, the user is allowed to add custom formatters e.g.
myformatter com.my.com.Formatter
which are aliased in the final format string as${now,myformatter,"dd.MM.yyyy"}
now I need to protect myself against someone registeringdate com.my.com.Formatter
so the predefined formats are using the “-” prefix and because the alias is spec’d in the DSL as a valid Java-Identifier I know it can not hold a dash 😉Pingback: e(fx)clipse 1.1.0 released | Tomsondev Blog
What does the FXML look like with this method?
What is bound to what in the FXML?
I tried with the XML text attribute have a leading “%” like normal FXML and that threw a exception at run time.
javafx.fxml.LoadException: No resources specified.
I tried without the leading “%” and the text is not translated.
A FXML snippet would help.
This system is not compatible with FXML-Resource loading. All you can do is to setup the binding between your UI-Labels and the MessageRegistry inside your controller