One of my recent java projects was to be used by users with three different languages. We went with the standard java approach of using properties files for messages. In intellij there is decent tool support for that.
However it seems a bit odd to have a strongly typed language and then rely on string keys for text lookup. At some point we introduced enums representing the keys, but as they were not automatically generated they involved a lot of repetition. Also you are never quite sure how many arguments a message needs.
So I had this idea of using interfaces to represent resource bundles. Each message could be represented as a method, with parameters representing the arguments to the placeholders. It would look somewhat like this:
public interface ExampleMessagePool { String sayHello(String name); String bye(String name); String warning(); }
From the clients point of view it would works as follows:
var factory = new MessagePoolFactory<ExampleMessagePool>(ExampleMessagePool.class); ExampleMessagePool english = factory.getLanguageSource("en"); assertEquals("Hello Matthieu!", english.sayHello("Matthieu")); assertEquals("Good bye Felix!", english.bye("Felix")); assertEquals("Attention!", english.warning()); ExampleMessagePool french = factory.getLanguageSource("fr"); assertEquals("Bonjour Matthieu!", french.sayHello("Matthieu")); assertEquals("Au revoir Felix!", french.bye("Felix")); assertEquals("Attention!", french.warning()); ExampleMessagePool german = factory.getLanguageSource("de"); assertEquals("Guten Tag Matthieu!", german.sayHello("Matthieu")); assertEquals("Auf Wiedersehen Felix!", german.bye("Felix")); assertEquals("Achtung!", german.warning());
The interesting point here is that I get completion and also a hint as of which arguments a particular message takes. The MessagePoolFactory takes care of creating instances for the respective languages. The next question is obviously, where these messages do come from. One way would be to use annotations:
@MessagePool(languages={"en", "de", "fr"}) public interface ExampleMessagePool { @Translations( entries = { @Entry(key = "en", value = "Hello {0}!"), @Entry(key = "de", value = "Guten Tag {0}!"), @Entry(key = "fr", value = "Bonjour {0}!") } ) String sayHello(String name); @Translations( entries = { @Entry(key = "en", value = "Good bye {0}!"), @Entry(key = "de", value = "Auf Wiedersehen {0}!"), @Entry(key = "fr", value = "Au revoir {0}!") } ) String bye(String name); @Translations( entries = { @Entry(key = "en", value = "Attention!"), @Entry(key = "de", value = "Achtung!"), @Entry(key = "fr", value = "Attention!") } ) String warning(); }
This approach is very sexy in so far as it allows to refactor method and parameter names. Also the implementation is trivial not least because java deals with unicode source files. On the other hand making your translators edit the java sources is probably a bit of a challenge. However you could provide a narrow view on that source code. The eclipse platform lends itself to that kind of experiment, so I actually did a bit of spike. Which I am demoing here:
Whilst this is very cool, it’s an entirely static approach. Especially with translations I find it beneficial if they are stored in a database so that they can be edited at run-time. Ideally some key users can then maintain translations, which are part of their domain anyway. Then the interface could be annotated with some sort of GUID, that could later be used at runtime to identify entries in the database:
public interface ExampleMessagePool { @Id("21850286-A914-4C13-88BA-75ACA2248B71") String sayHello(String name); @Id("DA3CD6BE-64E8-4501-A674-0789043CBD6F") String bye(String name); @Id("38211D99-BA2A-4572-9C23-976AC64181D8") String warning(); }
This way the refactorability would be preservered. Also tools support could be provided. Annotating elements with GUUIDs seems to be an interesting concept. Just imagine your database mapping being immune to name changes.
There is definitely some more thinking required here, but it strikes me that the default mechanisms in java are very rudimentary. Much less than a talented developer can dream up in a rainy afternoon.
Further developments could include static or, if you go for the runtime approach, dynamic tools to analyse, whether translations are present, or whether there are duplicates in message pools.
Leave a Reply