Skip navigation links

Package net.sf.mmm.util.nls.api

Provides the API for the native language support (NLS).

See: Description

Package net.sf.mmm.util.nls.api Description

Provides the API for the native language support (NLS).

NLS API

Applications that should be used by people all over the world need native language support (NLS). The developers task is the internationalization (i18n) where the application has to be written in a way that the code is (mostly) independent from locale-specific informations. This is a challenging task that affects many aspects like GUI-dialogs as well as all text-messages displayed to the end-user. The NLS provided here only addresses the internationalization of text-messages in a way that allows localization (l10n) to the users locale.

The Problem

Java already comes with great i18n support. But IMHO there are some tiny peaces missing to complete the great puzzle of NLS:
There is almost no support if an application needs NLS that is handling multiple users with different locales concurrently (e.g. a web-application).
You will typically store your messages in a ResourceBundle. Now if you store the technical key of the bundle in a message or exception the receiver needs the proper ResourceBundle to decode it or he ends up with a cryptic message he can NOT understand (e.g. as illustrated by the screenshot).
On the other hand you need to know the locale of the receiver to do the l10n when creating the message or exception with the proper text. This may lead to sick design such as static hacks. Also if you have to translate the text at the creation of the message every receiver has to live with this language. Especially for logging this is a big problem. An operator will be lost in space if he gets such logfiles:

 [2000-01-31 23:59:00,000][ERROR][n.s.m.u.n.a.MasterService] The given value (256) has to be in the range from 0 to 100.
 [2000-01-31 23:59:01,000][WARN ][n.s.m.u.n.a.MasterService] Der Benutzername oder das Passwort sind ungültig.
 [2000-01-31 23:59:02,000][ERROR][n.s.m.u.n.a.MasterService] 文件不存在。
 [2000-01-31 23:59:03,000][FATAL][n.s.m.u.n.a.MasterService] ข้อผิดพลาดที่ไม่คาดคิดได้เกิดขึ้น
 

The Solution

The solution is quite simple:
We simply bundle the message in default language together with the separated dynamic arguments in one container object that is called NlsMessage. For exceptions there is additional support via NlsException and NlsRuntimeException. Here is an example to clarify the idea of NlsMessage: The i18n message is "Hi {name}! How are you?" and the dynamic argument is the users name e.g. "Lilli". Now if we store these informations together we have all we need. To get the localized message we simply translate the i18n message to the proper language and then fill in the arguments. If we can NOT translate we always have the message in default language which is "Hi Lilli! How are you?".
But how do we translate the i18n message without artificial intelligence? The answer is quite easy:

Recommended Approach: NlsBundle

The recommended approach is to create an interface derived from NlsBundle. For each message you define a method that takes the arguments to fill in and returns an NlsMessage. Via annotations you provide the default message for each method.
 package foo.bar;

 public interface NlsBundleFooBarRoot extends NlsBundle {

   @NlsBundleMessage("Hi {name}! How are you?")
   NlsMessage messageSayHi(@Named("name") String name);

   @NlsBundleMessage("Sorry. The login \"{login}\" is already in use. Please choose a different login.")
   NlsMessage errorLoginInUse(@Named("login") String login);
 }
 
From your code you now can do this:
 String userName = "Lilli";
 NlsMessage msg = NlsAccess.getBundleFactory().createBundle(NlsBundleFooBarRoot.class).messageSayHi(userName);
 String textDefault = msg.getLocalizedMessage());
 String textDe = msg.getLocalizedMessage(Locale.GERMANY));
 
For the error message create an exception like this:
 public class LoginAlreadyInUseException extends NlsRuntimeException {
   public LoginAlreadyInUseException(String usedLogin) {

     super(createBundle(NlsBundleFooBarRoot.class).errorLoginInUse(usedLogin));
   }
 }
 
For further details see NlsBundle.

For localization you can create property files with the translations of your NLS-bundle. E.g. foo/bar/NlsBundleFooBar_de.properties with this content:
 messageSayHi = Hallo {name}! Wie geht es Dir?
 errorLoginInUse = Es tut uns leid. Das Login "{login}" ist bereits vergeben. Bitte wählen Sie ein anderes Login.
 
Unlike the Java defaults, here resource bundles are read in UTF-8 encoding and allow to use named parameters as alternative to indexes what makes it easier for localizers. There are even more advanced features such as recursive translation of arguments and choice format type. See NlsMessage for further details. Also our solution supports specific environments such as GWT (google web toolkit) what makes it very interoperable. In order to support you with creating and maintaining the localized properties, this solution also comes with the net.sf.mmm.util.nls.base.ResourceBundleSynchronizer.
The advantage is that also the bundle name and key are available in the NlsMessage and that this approach is GWT compatible when using mmm-util-gwt. However, there is still our legacy approach.

Alternate Approach (legacy): AbstractResourceBundle

Simply create a subclass of AbstractResourceBundle that declares public string constants:
 package foo.bar;

 public class FooBarResourceBundle extends AbstractResourceBundle {
   public static final String MSG_SAY_HI = "Hi {name}! How are you?";
   public static final String ERR_LOGIN_IN_USE = "Sorry. The login \"{login}\" is " +
     "already in use. Please choose a different login.";
 }
 
From your code you only need to create the NlsMessage using this constants:
 String userName = "Lilli";
 NlsMessage msg = NlsAccess.getFactory().create(FooBarResourceBundle.MSG_SAY_HI, "name", userName);
 String textDefault = msg.getLocalizedMessage());
 String textDe = msg.getLocalizedMessage(Locale.GERMANY));
 
For the error message create an exception like this:
 public class LoginAlreadyInUseException extends NlsRuntimeException {
   public LoginAlreadyInUseException(String usedLogin) {
     super(MyResourceBundle.ERR_LOGIN_IN_USE, toMap(KEY_NAME, usedLogin));
   }
 }
 
For the automatic reverse-lookup create the file META-INF/net.sf.mmm/nls-bundles with the fully qualified name of your bundle-class (foo.bar.FooBarResourceBundle) as content.
For localization you can create property files as described above in the recommended approach. In order to support you with creating and maintaining the localized properties, this solution also comes with the net.sf.mmm.util.nls.base.ResourceBundleSynchronizer.

Conclusion

As we have seen the NLS provided here makes it very easy for developers to write and maintain internationalized code. While messages are created throughout the code they only need to be localized for the end-user in the client and at service-endpoints. Only at these places you need to figure out the users locale (see UserSessionProvider).
Skip navigation links

Copyright © 2001–2016 mmm-Team. All rights reserved.