GSIP 75 - Exception I18n

Overview

Adding ability to localize exception messages.

This proposal is actually implemented as part of GSIP 71 - New Security Subsystem.

Proposed By

Justin Deoliveira

Assigned to Release

2.2

State

Under Discussion, In Progress, Completed, Rejected, Deferred

Motivation

Currently Geoserver supports i8n of the web admin interface, but lacks localized error messages from exceptions. The goal of this proposal is to provide a mechanism for localizing exception messages that translators are familiar with.

What is not the goal of this proposal is to do a comprehensive localization of every exception message emitted by GeoServer. This is meant to be built-up over time, the same way translations of the wicket ui are.

Proposal

This proposal has two main parts. The first is a set of base classes that all localizable exceptions extend from. The second is a mechanism for adding properties files (resource bundles) containing exception message translations.

GeoServerException and GeoServerRuntimeException

The idea here is to have two exception base classes, one for checked and one for non-checked exceptions, that all localizable exceptions in GeoServer extend from. These subclasses add two additional properties:

  • id - An identifier for the exception
  • args - Arguments to pass to the localized exception message

The following is an excerpt from the GeoServerException class:

public class GeoServerException extends Exception {

    /** id for the exception, used to locate localized message for the exception */
    String id;

    /** arguments to pass into the localized exception message */
    Object[] args;

    @Override
    public String getMessage() {
        if (id == null) {
            return super.getMessage();
        }

        String localized = GeoServerExceptions.localize(this);
        return localized != null ? localized : super.getMessage();
    }

}

The getMessage() method is overridden and a utility method, GeoServerExceptions.localize, is delegated to. The localize method does two things.

  1. Locates the bundle containing translated messages for the exception class
  2. Looks up the message key based on exception id

Exception Bundles

Exception messages are stored in property files (known as bundles), named with a base name of "GeoServerException". The naming convention is the same as that of the web ui message bundles, with the default bundle file being named "GeoServerException.properties", and a two letter code appended onto the base name for each supported locale.

The location of the message bundles also mirrors of that of the web ui in that they are located in the root of a module. The contents of the message bundles are key value pairs in which the keys consist of an exception class name and exception id pair, and the values being the translated messages. For example consider the following entry.

XMLSecurityConfigException.FILENAME_CHANGE_INVALID=Cannot change file name from {0} to {1}

In the above "XMLSecurityConfigException" is the non-qualified exception class name, and "FILENAME_CHANGE_INVALID" is the exception identifier. This example also shows a translated message containing two parameters.

The exception class name can be specified as qualified or non-qualified. The qualified version is also looked up first, and if no matching key is found the non-qualified version is used.

Locale and Lookup Order

The lookup order of exception message bundles depends on the server locale. First the default locale of the server Locale.getDefault() is used to locate a message bundled using the two letter language code of the locale. For instance a server a default local of german would look for a file named "GeoServerException_de.properties". If no such bundle is found the language code is dropped and the default file "GeoServerException.properties", that contains the English translation, is looked up.

In the event no file is found period, not even a default, the exception message is left un-localized and the exception message is simply the exception identifier.

Example

Let us consider a full example of creating a localized exception. First the exception class itself:

public class FooException extends GeoServerException {

   public static final String HELLO_$1 = "HELLO";

   public FooException(String id, Object[] args) {
     super(id);
     setArgs(args);
   } 
}

We create public constants on the class for each of the exception identifiers to implement. Throwing this exception would look something like:

throw new FooException(HELLO_$1, new Object[]{"Bob"});

In the root of the module we would then create a default message bundle named "GeoServerException.properites" with the following contents:

FooException.HELLO=Hello {0}

And additional bundles for each supported language, for example "GeoServerException_fr.properties":

FooException.HELLO=Bounjour {0}

Feedback

This section should contain feedback provided by PSC members who may have a problem with the proposal.

Backwards Compatibility

None.

Voting

Andrea Aime:
Alessio Fabiani:
Ben Caradoc-Davies:
Gabriel Roldán:
Justin Deoliveira:
Jody Garnett:
Mark Leslie:
[~roba]:
Simone Giannecchini:

Links

[JIRA Task|]
[Email Discussion|]
[Wiki Page|]

Added by Justin Deoliveira, last edited by Justin Deoliveira on Apr 04, 2012  (view change)
View Attachments (0) Info