Где разместить ключевые строки i18n в Java

При интернационализации в Java вы назначаете строковый ключ каждому сообщению. Какая лучшая практика, где разместить эти строковые ключи. Цель состоит в том, чтобы разрешить простой рефакторинг (например, изменение имени ключа), чистый и читаемый код, разделение проблем, но при этом не дублировать ключи/сообщения, даже если они вызваны из разных частей кода.

//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);
}

Любые другие предложения? Что лучше? Я на Eclipse IDE, если это делает часть рефакторинга более понятной.

Разъяснение: в приведенных выше примерах "сообщения" имеют тип ResourceBundle.

Ответ 1

Я всегда использую для такого материала интерфейс, в котором перечислены мои ключи. Имя Interace в основном DESC = Issue/short descriptiontion/topic и значения ключей. Таким образом, вы можете создать приятный интерфейс и некоторый общий интерфейс, например. для клавиш OK или Abort.

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

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

Ответ 2

В принципе, кажется, мы все согласны с тем, что нужна какая-то константа. Когда дело доходит до констант, я сильно предпочитаю Enums. Java Enums очень мощные и определенно недоиспользуемые:

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

ОК, но что я должен был сделать, чтобы он выглядел так? Простой интерфейс, перечисление и небольшая модификация стандартной процедуры доступа к сообщениям. Начните с интерфейса:

public interface MessageKeyProvider {
    String getKey();
}

Перечисление:

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;
    }
}

И модифицированный метод getString():

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

Чтобы завершить изображение, давайте посмотрим RunDialog.properties(я скоро остановлюсь):

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

Очевидно, вы могли бы использовать Enum для чтения из файла свойств (путем вложения ResourceBundle), однако это, вероятно, нарушит принцип единой ответственности (а также не повторяйте себя, поскольку код доступа нужно будет повторить).

Возвращаясь к файлу свойств, у меня возникло чувство (возможно, я ошибаюсь здесь), что одной из ваших целей было избежать дублирования переводов. Вот почему я поставил два Runs в приведенном выше примере. Понимаете, это слово будет переведено по-другому в зависимости от контекста (что на самом деле довольно распространено). В этом примере, если бы я перевел это на польский, это будет выглядеть так:

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

Это неудачная проблема какого-то странного языка, имеющего понятие спряжения...

Ответ 3

Я также считаю, что первым является худший выбор. В большинстве случаев (ключ используется только одним классом) я предпочел бы второе решение со строковыми константами.

Если ключ ссылается на более чем один класс, соседний класс является лучшим способом (используя упомянутый интерфейс, например, @moohkooh).

Решение с одним центральным классом создает магнит зависимости, который, по моему мнению, является плохим дизайном. Соседние интерфейсы с константами на один пакет будут лучше.

Если вы не хотите, чтобы интерфейс удерживал константы, вы можете использовать обогащенное перечисление:

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;
  }
}

Это можно использовать как:

messages.getString(DESCMessage.HELLO.key());

Ответ 4

Строковые константы - это путь. Где вы их определяете, это действительно зависит от структуры кода и использования ключей. Например:

  • Если вы используете только ключи в одном классе, лучше их разместить там.
  • Если вы повторно используете одни и те же ключи в своем коде, лучше поместить их в класс-помощник (глобальный или пакетный класс в зависимости от использования ключа и количества ключей).

С точки зрения рефакторинга это немного сложнее (требуется больше изменений), чтобы переместить константы из одного класса в другой, чем переименовать их или изменить их значение.

При изменении значения вы не можете автоматически изменять определенный ресурс.

Ответ 6

IMHO ResourceBundle облегчает использование файлов свойств конкретных локалей. Чтобы использовать ResourceBundle, файлы свойств должны быть названы в соответствии со следующим соглашением: -

BaseName_langCode.properties

ИЛИ

BaseName_langCode_countryCode.properties

Вы можете использовать файлы свойств.

Ответ 7

IMHO Лучшим местом для определения этих ключей и связанных с ними строк являются файлы NLS. n И вы должны сохранить их в файлах ResourceBundle.