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


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:

  1. One class file to hold the translation values (MyMessages)
    public class MyMessages {
       public String mySimpleMessage;
    }
    
  2. 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);
      }
    }
    
  3. 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!

So look at this
l10n

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

l10-ref

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);
  }
}
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 – tooling

  1. WhoCares says:

    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 &lt;, &gt; and &quot;

  2. WhoCares says:

    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() );
    }

    • Tom Schindl says:

      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 registering date 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 😉

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

  4. Paulhr says:

    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.

    • Tom Schindl says:

      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

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.