środa, 28 maja 2008

Lokalizacja wyjątków w Javie

Podczas pisania dużych aplikacji konieczne jest często stworzenie różnych wersji językowych. Dzisiejszego dnia zajmiemy się lokalizacją wyjątków, aby ładnie i czytelnie informowały użytkowników z całego świata, że nasza aplikacją się rozkraczyła :)))

Zaczynamy od stworzenia klasy przechowującej wiadomość wyjątku. Do tego zaprzęgamy klasę ListResourceBundle którą bardzo ładnie przechowuje nam pary klucz-wartość.
Najpierw stworzymy klasę przechowującą wiadomość która zostanie wyświetlona gdy nie zostanie znaleziona inna dla konkretnego języka.

ExceptionResourceBundle.java:

import java.util.ListResourceBundle;

public class ExceptionResourceBundle extends ListResourceBundle {

private static final Object[][] contents = { { "criticalException",
"Wystąpił błąd krytyczny" } };

public Object[][] getContents() {
return contents;
}
}

Ok, teraz można stworzyć jakieś lokalizacje.
Wersja językowa aplikacji wybierana jest poprzez kombinację symbolu określającego język oraz kraj. Np
de - język niemiecki
en-US - język angieski, kraj USA
itp. takie kody są normami ISO.
Zgodnie z tymi kodami wybierana jest klasa przechowująca wiadomość w danym języku. Np dla języka francuskiego bedzie to [nazwa_klasy]_fr, dla angielskiego USA [nazwa_klasy]_en_US (w Javie nie można używać znaku '-' w nazwie klasy wiec zastępujemy go przez '_').
No to zróbmy wersje wyjątku w języku angielskim dla kraju USA...

ExceptionResourceBundle_en_US.java:

public class ExceptionResourceBundle_en_US extends ExceptionResourceBundle {
private static final Object [][] contents = {
{"criticalException", "Critical error!"}
};
public Object [][] getContents(){ return contents; }
}

wersja francuskojęzyczna...

ExceptionResourceBundle_fr.java:

public class ExceptionResourceBundle_fr extends ExceptionResourceBundle{
private static final Object [][] contents = {
{"criticalException", "Il y'a quelque chose qui cloche!"}
};
public Object [][] getContents(){ return contents; }
}

Dziedziczymy po ExceptionResourceBundle aby uniknąć powtarzania wpisów których nie lokalizujemy.
Skoro mamy zlokalizowane komunikaty to trzeba by stworzyć zlokalizowany wyjątek:)
Kody krajów i języków przechowuje klasa Locale. Wyboru odpowiedniej klasy ze zlokalizowaną wiadomością dokonuje metoda ReosourceBundle.getBundle(String baseName, Locale locale). Zwraca ona obiekt najbardziej pasujący dla danych kodów zawartych w obiekcie locale.
No to piszemy naszą klasę wyjątku. Główne znaczenie ma tutaj przeciążona metoda getLocalizedMessage().

LocalizedException.java:

import java.util.Locale;
import java.util.ResourceBundle;

public class LocalizedException extends Exception {
public static final String DEFAULT_MESSAGE_KEY = "criticalException";
private String localeMessageKey = DEFAULT_MESSAGE_KEY;
private String languageCode;
private String countryCode;

public LocalizedException(String message) {
super(message);
}

public LocalizedException(Throwable cause, String messageKey) {
super(cause);
if (isValidString(messageKey)) {
localeMessageKey = messageKey;
}
}

public LocalizedException(String defaultMessage, Throwable cause,
String messageKey) {
super(defaultMessage, cause);
if (isValidString(messageKey)) {
localeMessageKey = messageKey;
}
}

public LocalizedException(String defaultMessage, String messageKey,
String language, String country) {
super(defaultMessage);
if (isValidString(messageKey)) {
localeMessageKey = messageKey;
}
if (isValidString(country)) {
countryCode = country;
}
if (isValidString(language)) {
languageCode = language;
}
}

public void setLocaleMessageKey(String messageKey) {
if (isValidString(messageKey)) {
localeMessageKey = messageKey;
}
}

public String getLocaleMessageKey() {
return localeMessageKey;
}

public String getLocalizedMessage() {
ResourceBundle rb = null;
Locale locale = getLocale();
rb = ResourceBundle.getBundle("ExceptionResourceBundle", locale);
return rb.getString(localeMessageKey);
}

private Locale getLocale() {
Locale locale = Locale.getDefault();
if ((languageCode != null) && (countryCode != null)) {
locale = new Locale(languageCode, countryCode);
} else if (languageCode != null) {
locale = new Locale(languageCode);
}
return locale;
}

private boolean isValidString(String input) {
return (input != null) && (!input.equals(""));
}
}

I to wszystko :) Teraz napiszmy krótki programik testujący nasz wyjątek.

LocalizedExceptionTest.java:

public class LocalizedExceptionTest{
public static void main(String [] args){
LocalizedExceptionTest application = new LocalizedExceptionTest();
application.testLocalizedException("Angielski - USA:", "en", "US");
application.testLocalizedException("Domyślny:", null, null);
application.testLocalizedException("Francuski:", "fr", "");
application.testLocalizedException("Włoski:", "it", "");
}

public void testLocalizedException(String message, String language, String country){
try{
throw new LocalizedException("Just testing!", "", language, country);
}
catch (LocalizedException exc){
System.err.println(message + " " + exc.getLocalizedMessage());
}
}
}

Po uruchomieniu konsola powinna nam wypluć:
Angielski - USA: Critical error!
Domyślny: Wystąpił błąd krytyczny
Francuski: Il y'a quelque chose qui cloche!
Włoski: Wystąpił błąd krytyczny

Dla włoskiego dostaliśmy wiadomość po polsku gdyż nie została zlokalizowana klasa odpowiedzialna za ten język, dlatego użyto wartości domyślnej.

Mam nadzieje, że kiedyś komuś się przyda ten art ;-)
Pozdrawiam i zapraszam do komentowania i zadawania pytań w razie niejasności.

Brak komentarzy:

Prześlij komentarz