Blog

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);
  }
}
published on
Previous
e(fx)clipse 1.1 – New features - API to restart your e4 app on startup