Module io.github.mmm.nls
Native Language Support (NLS)
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
ApplicationException
. 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 this information 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 to other languages? The answer is quite easy:
NlsBundle
The recommended approach is to create a final class derived fromNlsBundle
. For each message you define a method that takes the arguments to fill in and
returns an NlsMessage
:
package foo.bar; public final class NlsBundleExample extendsFrom your code you now can do this:NlsBundle
{ public static final NlsBundleExample INSTANCE = new NlsBundleExample();NlsMessage
messageSayHi(String name) { return create("messageSayHi", "Hi {name}! How are you?",NlsArguments.ofName
(name)); }NlsMessage
errorLoginInUse(String login) { return create("errorLoginInUse", "Sorry. The login '{name}' is already in use. Please choose a different login.",NlsArguments.ofName
(login)); } }
String userName = "Lilli";For the error message create an exception like this:NlsMessage
msg = NlsBundleExample.INSTANCE.messageSayHi(userName); String text = msg.getMessage
()); String textDefault = msg.getLocalizedMessage
()); String textDe = msg.getLocalizedMessage
(Locale.GERMAN
));
public class LoginAlreadyInUseException extends ApplicationException
{
public LoginAlreadyInUseException(String login) {
this(null, login);
}
public LoginAlreadyInUseException(Throwable cause, String login) {
super(NlsBundleExample.INSTANCE.errorLoginInUse(login), cause);
}
}
For further details see NlsBundle
. For localization you can create property files with the translations of your NLS-bundle. E.g.
foo/bar/NlsBundleExample_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, you will use named parameters instead of indexes what makes it much easier for localizers. There are even more advanced features such as recursive translation of arguments and choice format type. See
NlsMessage
for further details.
In order to support you with creating and maintaining the localized properties, this solution also comes with the
io.github.mmm.nls.sync.NlsSynchronizer
. 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 (e.g. usingorg.springframework.context.i18n.LocaleContextHolder
).
- The
NlsMessage
allows to store an internationalized message together with actual arguments to fill in. - The arguments can be arbitrary objects including
LocalizableObject
s that will be localized recursively. - There are powerful ways to format these arguments including variable expressions for optional arguments or plural
forms. See
NlsMessage
for advanced examples. - Instead of numbered arguments we support named arguments what makes maintenance of the messages a lot easier. Your localizers will love you for choosing this solution.
- Resource bundle properties are read in UTF-8 encoding making it easier for localizers as they do not have to escape characters to unicode number sequences.
- The localization (translation to native language) is easily performed by
NlsMessage.getLocalizedMessage(java.util.Locale)
. - For exceptions there is additional support via
ApplicationException
.
-
-
Packages
Exports Package Description io.github.mmm.nls Provides the main API for the native language support (NLS).io.github.mmm.nls.argument Contains the API for the dynamic arguments of aNlsMessage
.io.github.mmm.nls.descriptor Contains descriptors providing meta-information for bundle and key.io.github.mmm.nls.exception io.github.mmm.nls.formatter Contains formatters to format NLS messages.io.github.mmm.nls.template io.github.mmm.nls.variable Contains types for parsed variable placeholders of a NLS message.
-
Modules
Requires Modifier Module Description transitive io.github.mmm.scanner Provides scanners that help to parse character sequences efficient and easily.Indirect Requires Modifier Module Description transitive io.github.mmm.base Provides fundamental APIs and helper classes.
-