Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where to place i18n key strings in Java

When doing internationalization in Java, you assign a string key to each message. What's the best practice, on where to place those string keys. Goal is to allow easy refactoring (eg. key name changes), clean and readable code, separation of concerns but still no duplication of keys/messages even if called from different parts of the code.

//bad way, strings directly in code
messages.getString("hello_key");

-

// better way, use String constants
public static final String HELLO_KEY = "hello_key";
...
messages.getString(HELLO_KEY);

-

// other (better?) way, put all keys in one huge central class
public class AllMessageKeys {
  public static final String HELLO_KEY = "hello_key";
  ...
}

public class Foo {
  ...
  messages.getString(AllMessageKeys.HELLO_KEY);
}

-

// other (better?) way, put all keys in neighbor class
public class FooMessageKeys {
  public static final String HELLO_KEY = "hello_key";
}

public class Foo {
  ...
  messages.getString(FooMessageKeys.HELLO_KEY);
}

Any other proposals? Which is best? I'm on Eclipse IDE, if that makes the refactoring part any clearer.

Clarification: in the above examples "messages" is of type ResourceBundle.

like image 838
Philipp Avatar asked Aug 09 '11 07:08

Philipp


People also ask

What is i18n in Java?

Internationalization (i18n) is the process of making your application capable of rendering its text in multiple languages. Localization (l10n) means your application has been coded in such a way that it meets language, cultural, or other requirements of a particular locale.

How can I achieve internationalization in Java?

We can implement Internationalization by using the following three classes: Locale. NumberFormat. DateFormat.


2 Answers

Basically, it seems that we all agree that some kind of constant is needed. When it comes to constants, I strongly prefer Enums. Java Enums are very powerful and definitely underused:

String title = Messages.getString(RunDialogMessages.TITLE);

OK but what I had to do to make it look like this? A simple interface, an enum and slight modification to standard message access routine. Let's start with the interface:

public interface MessageKeyProvider {
    String getKey();
}

The enum:

public enum RunDialogMessages implements MessageKeyProvider {
    TITLE("RunDialog.Title"),
    PROMPT("RunDialog.Prompt.Label"),
    RUN("RunDialog.Run.Button"),
    CANCEL("RunDialog.Cancel.Button");


    private RunDialogMessages(String key) {
        this.key = key;
    }

    private String key;

    @Override
    public String getKey() {
        return key;
    }
}

And modified getString() method:

public static String getString(MessageKeyProvider provider) {
    String key = provider.getKey();
    try {
        return RESOURCE_BUNDLE.getString(key);
    } catch (MissingResourceException e) {
        return '!' + key + '!';
    }
}

Just to complete the picture, let us see RunDialog.properties (I will make a point about it soon):

RunDialog.Title=Run
RunDialog.Prompt.Label=Enter the name of the program to run:
RunDialog.Run.Button=Run
RunDialog.Cancel.Button=Cancel

Obviously, you could use Enum to read from properties file (by embedding ResourceBundle), however it would probably violate Single Responsibility Principle (as well as Don't Repeat Yourself, as access code would need to be repeated).

Going back to properties file, I had a feeling (I might be wrong here), that one of your goals was to avoid duplicating the translations. That's why I put two Runs in example above. You see, this word would be translated in a different way depending on the context (which is actually quite common). In this example, if I were to translate that to Polish it would look like this:

RunDialog.Title=Uruchamianie
RunDialog.Prompt.Label=Wpisz nazwę programu do uruchomienia:
RunDialog.Run.Button=Uruchom
RunDialog.Cancel.Button=Anuluj

That is unfortunate problem of some strange language that have a concept of conjugation...

like image 85
Paweł Dyda Avatar answered Oct 05 '22 13:10

Paweł Dyda


I also think the first is the worst choice. In most cases (the key is only used by one class) I would prefer the second solution with String constants.

If the key is referenced from more than one class, the neighbor class is a better way (using an interface like @moohkooh mentioned).

The solution with one central class creates a dependency magnet which is a bad design in my opinion. Neighbor interfaces with constants per package would be a better one.

If you do not want a interface to hold the constants, you can use an enriched enum:

public enum DESCMessage {

  HELLO("hello_key"),
  OTHER("other_key");

  private final String key;

  private DESCMessage(String key) {
    this.key = key;
  }

  public String key() {
    return key;
  }
}

This can be used as:

messages.getString(DESCMessage.HELLO.key());
like image 23
Arne Burmeister Avatar answered Oct 05 '22 11:10

Arne Burmeister