{ by david linsin }

April 28, 2008

How to Internationalize Exceptions II

In my previous post I asked for some advice on how to internationalize Exceptions. You guys gave me some great feedback.

Anders raised a valid issue, which I had not considered before. In terms of layering, he thinks it's the view's responsibility to internationalize error messages. I tend to agree - however, I came across the requirement to log internationalized error messages and send them by email. Usually it's not the view layers responsibility to send emails, so you need to think of internationalization almost as some kind of cross cutting concern.

I guess in most projects, error codes/messages are managed the same way Martin described it: you have static final ints in a separate interface. It is imported into every class that handles Exceptions. This might be straight forward in the beginning, but could turn into a maintenance nightmare after that interface grew to a couple hundred lines. I admit, I'm guilty of it too and that's the reason why I wanna get it right in my current project.

After I read the article and checked the corresponding source code that Alex had suggested, I investigated a little further and could find any more information on this topic. So I decided to come up with something myself, based on your input.

How about 2 abstract classes as a template for Exception and RuntimeException. They form a contract for an internationalized Throwable. Below is a sample implementation for Exception.

public abstract class I18NException extends Exception {
public enum Message implements ExceptionMessage {
DEFAULT("default.error");

private String key;
private Message(String argKey) {
key = argKey;
}

public String getLocalizedMessage(Locale argLocale) {
return ResourceBundle.getBundle("ErrorResources", argLocale).getString(key);
}

}
private Locale locale;
private Message message;

public I18NException(Locale argLocale, Message argMessage, String arg0, Throwable arg1) {
super(arg0, arg1);
locale = argLocale;
message = argMessage;
}
/** other constructors omitted **/

public String getLocalizedMessage() {
return message.getLocalizedMessage(locale);
}
}

You can use this class as a template for all other Exceptions that need to be internationalized. The default enum is optional, you can omit it if you don't need it. The interface ExceptionMessage defines a contract between the enum responsible for error messages and the Exception class containing it.

public interface ExceptionMessage {
public String getLocalizedMessage(Locale argLocale);
}

So if you want to implement your own internationalized Exception all you have to do is subclass I18NException with it's constructors and add an enum implementing the ExceptionMessage interface. The contract enforced by ExceptionMessage makes sure that getLocalizedMessage is called by the abstract base class.

I think this design is pretty straightforward and easy to use. You don't have to implement a lot of code to get your internationalized error messages working and you have a clean design enforced by contracts. The only alien thing for me is the Local instance that you have to pass along in the constructor. I couldn't think of any other way to pass it along, so the constructor seemed to be a nice way to define that interface.

Let me know what you think of this solution. I'm pretty sure there is a lot of room for improvements and I'm keen to know about it.

5 comments:

Anonymous said...

This is very similar to a class I had to implement running under J2EE. We opted not to carry around a Locale variable and instead have getMessage() use the default Locale. This turned out to be a bit cleaner in the end: debugging exceptions in the server's stdout/stderr were always logged in Locale.en_US, exceptions passed to the web client were displayed in the session Locale, and all other messages were displayed in the appserver Locale. So all three possibilities were covered.

One other thing: it's critical that your ResourceBundle (.properties files) includes ALL of the languages you might need to see, including the one that corresponds to the default locale. You the same exception to translate in both the server locale (what your user sysadmins will see) and in your developer's locale for debugging. I mention this only because I once had trouble convincing the team that the server might really be in non-English.

david said...

I agree that, if you want to log exception messages in different languages or with different Locales it would be a pain to use my approach.

The reason for sticking to a Locale was to comply with the Throwable contract. From a practical point of view I guess your solution might be a better one.

Giorgio said...

But what is this used for? I think only developers have to see exceptions, and developers know english..

david said...

hey giorgio,

usually you would handle error states or plausibility checks that fail in your application by using some kind of Exception. Your end user typically gets an error message either by rendering it on a web page or in a dialog that pops up in your desktop app. So eventually your user won't see the actual Exception, but rather the error message held by the raised Exception. And that's exactly what I'm trying to come up with here.

I totally disagree with the statement that all developers know english. They might be able to understand, but producing reasonable error messages is hard - no matter which language use.

david said...

I found an hold blog post of mine, providing a link to an article covering Exception handling in Java EE. It might be interesting for you guys: http://blog.linsin.de/archives/...


com_channels

  • mail(dlinsin@gmail.com)
  • jabber(dlinsin@gmail.com)
  • skype(dlinsin)

recent_postings

loading...