Как избежать повторения строк Java ResourceBundle?

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

Решение, которое я придумал, выглядело примерно так:

public class ExpandingResourceBundleControl extends ResourceBundle.Control {
  public static final ResourceBundle.Control EXPANDING =
    new ExpandingResourceBundleControl();

  private ExpandingResourceBundleControl() { }

  @Override
  public ResourceBundle newBundle(String baseName, Locale locale, String format,
                                  ClassLoader loader, boolean reload)
    throws IllegalAccessException, InstantiationException, IOException {

      ResourceBundle inner = super.newBundle(baseName, locale, format, loader, reload);
      return inner == null ? null : new ExpandingResourceBundle(inner, loader);
  }
}

ExpandingResourceBundle делегирует реальный пакет ресурсов, но выполняет преобразование {{this.kind.of.thing}} для поиска ключа в ресурсах.

Каждый раз, когда вы хотите получить один из них, вы должны пойти:

ResourceBundle.getBundle("com/acme/app/Bundle", EXPANDING);

И это прекрасно работает - какое-то время.

Что в конечном итоге происходит, так это то, что какой-то новый код (в нашем случае автоматически сгенерированный код, который был выброшен из Matisse) ищет тот же пакет ресурсов без указания пользовательского элемента управления. Это кажется невоспроизводимым, если вы пишете простой модульный тест, который вызывает его с и без, но это происходит, когда приложение запускается по-настоящему. Каким-то образом кеш внутри ResourceBundle извлекает хорошее значение и заменяет его неработающим. Мне еще предстоит выяснить, почему, и файлы jar Sun были скомпилированы без отладочной информации, поэтому отладка - это рутинная работа.

Мои вопросы:

  1. Есть ли способ глобально установить ResourceBundle.Control по умолчанию, о котором я мог не знать? Это решило бы все довольно элегантно.

  2. Есть ли какой-то другой способ элегантно справиться с такими вещами, возможно, вообще не вмешиваясь в классы ResourceBundle?


person Trejkaz    schedule 09.04.2010    source источник
comment
Это то, с чем можно было бы легко справиться с помощью метапрограммирования, если бы оно было в Java.   -  person Gabriel Ščerbák    schedule 09.04.2010


Ответы (2)


Я думаю, что это фундаментальный недостаток в способе функционирования ResourceBundles: ключи, которые ссылаются на другие ключи, автоматически нарушают принцип DRY (не повторяйтесь). То, как я обошел это, было похоже на ваш метод: создайте класс ReflectiveResourceBundle, который позволяет вам указывать ключи ресурсов в сообщениях с использованием нотации EL.

НЕПРАВИЛЬНЫЙ СПОСОБ:

my.name.first=Bob
my.name.last=Smith
my.name.full=Bob Smith

ПРАВИЛЬНЫЙ СПОСОБ:

my.name.first=Bob
my.name.last=Smith
my.name.full=${my.name.first} ${my.name.last}

Я загрузил код на GitHub, чтобы вы или кто-либо другой мог его скачать. Кроме того, я добавил пример кода для тех, кто использует Stripes Framework (http://www.stripesframework.org/), чтобы вы могли быстро приступить к работе.

Хитрость в том, чтобы заставить это работать со стандартными JSTL fmt taglibs, заключалась в настройке перехватчика, который заменял ресурс HttpServletRequest нашим собственным. Код выглядит примерно так:

ResourceBundle bundle = MyStaticResourceHoldingTheBundle.getBundle();
Config.set(request, Config.FMT_LOCALIZATION_CONTEXT, new LocalizationContext(bundle, locale));

Взгляните на пакет stripes.interceptor в приведенной выше ссылке для более подробной информации.

person Matt Brock    schedule 11.04.2010
comment
Вы правы, это очень похоже на наше решение и имеет тот же недостаток, который заключается в том, что сгенерированный код с использованием ResourceBundle.getBundle() не получит обходного пути. Основное отличие заключается в том, что в нашем случае мы используем существующий кеш ResourceBundle, тогда как в вашем случае вы храните кеш отдельно, предположительно для обхода кеша Sun, который время от времени затирает кешированные пакеты. - person Trejkaz; 12.04.2010
comment
Потому что я просто случайно наткнулся на ту же проблему: я в основном хотел повторно использовать все функции ResourceBundle, но добавить расширение ключа. Итак, я получил от PropertyResourceBundle и соответственно переопределил handleGetObject() (как видно из исходников Мэтта Брокса). Затем я получил от ResourceBundle.Control, переопределил newBundle(), чтобы вернуть свой пользовательский PropertyResourceBundle (только что изменил исходный код для этого метода) и передал его в ResourceBundle.getBundle(). Это позволяет повторно использовать все функции ResourceBundle + добавить расширение ключа. - person quaylar; 05.09.2012
comment
@ Мэтт Брок, кажется, ссылка не работает. Вы можете поделиться страницей? - person Mohammad Adnan; 23.03.2015

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

  1. «Красношапочный малиновка — маленькая воробьиная птица, обитающая в Австралии».
  2. «Красношапочный малиновка обитает в более засушливых регионах на большей части континента».

Набор ресурсов может быть следующим:

robin.name=The Red-capped Robin
robin.native=is a small passerine bird native to Australia.
robin.region=is found in dryer regions across much of the continent.

а затем объединить необходимые части, где это необходимо bundle.getString("robin.name")+bundle.getString(robin.native).

Одна вещь, с которой вам нужно быть осторожным, заключается в том, что правила грамматики, такие как порядок подлежащих предикатов и т. д., могут быть не одинаковыми во всех языках. Так что вам нужно быть немного осторожным при разделении предложений.

person Prachi    schedule 09.04.2010
comment
На самом деле это также плохая идея для разных контекстов (например, в меню или в строке состояния), пола субъекта и множества (1, 2, 27 и т. д.), поскольку они также меняются в зависимости от культуры. - person devstuff; 09.04.2010
comment
На самом деле именно по этой причине мы выбрали подход выполнения всех вставок со стороны ResourceBundle. С нашим текущим подходом этот пример можно было бы сделать, поместив {{robin.name}} в соответствующее место в значениях под ним. Если кто-то столкнулся с языком, в котором он не работает, он может полностью удалить переменную. Но... наше решение, конечно, сложно заставить работать правильно. - person Trejkaz; 10.04.2010