Spring - получить все разрешимые ключи сообщений - PullRequest
14 голосов
/ 02 августа 2011

У меня есть веб-приложение, которое использует Spring MVC. Я хотел бы, чтобы мой интерфейс состоял из одной страницы, которая динамически извлекала все данные в виде JSON через AJAX. Моя проблема с интернационализацией. Когда я визуализирую контент в jsps, я могу использовать JSTL-теги для разрешения моих ключей (очень легко с Spring MVC):

<fmt:message key="name"/>: ${name}
<fmt:message key="title"/>: ${title}
<fmt:message key="group"/>: ${group}

При правильной настройке он отображается на финском языке как

Nimi: yay
Otsikko: hoopla
Ryhmä: doo

Теперь, когда я использую json, я получаю только следующее сообщение от сервера:

{
 name: "yay", 
 title: "hoopla",
 group: "doo"
}

Там нет имен ключей! Но я должен предоставить их как-то. Я подумал об изменении имен ключей на их локализованные формы или добавлении локализованных имен ключей в вывод json (например, name_localized = "Nimi"), но оба эти варианта кажутся плохой практикой. Я использую Jackson JSON для автоматического разбора моих доменных объектов в JSON, и мне нравится инкапсуляция, которую он обеспечивает.

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

<script type="text/javascript">
var name="Nimi";
var title="Otsikko";
var group="Ryhmä";
</script>

Как только я загрузил это, у меня теперь есть вся информация в моем javascript для обработки json! Но есть одна проблема: мой список имен полей является динамическим. Так что на самом деле статический рендеринг в jsp будет выглядеть так:

<c:forEach var="field" values="${fields}">
 <fmt:message key="${field.key}"/>: ${field.value}
</c:forEach>

Мне нужно найти все сообщения, указанные в моих messages.properties. Интерфейс Spring MessageSource поддерживает получение сообщений только по ключу. Как я могу получить список имен ключей в моем JSP, который отображает локализованные переменные javascript?

Ответы [ 5 ]

13 голосов
/ 08 августа 2011

Что ж, я "решил" мою проблему, расширив ResourceBundleMessageSource.

package org.springframework.context.support;

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

public class ExposedResourceBundleMessageSource extends
        ResourceBundleMessageSource {
    public Set<String> getKeys(String basename, Locale locale) {
        ResourceBundle bundle = getResourceBundle(basename, locale);
        return bundle.keySet();
    }
}

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

Set<String> keys = 
  ((ExposedResourceBundleMessageSource)
  messageSource).getKeys("messages", locale);
4 голосов
/ 22 октября 2011

Я решил эту проблему.


public class ExposedResourceBundleMessageSource extends
        ResourceBundleMessageSource {
    public static final String WHOLE = "whole";
    private Set baseNames;
    private Map> cachedData = new HashMap>();

    public Set getKeys(String baseName, Locale locale) {
        ResourceBundle bundle = getResourceBundle(baseName, locale);
        return bundle.keySet();
    }

    public Map getKeyValues(String basename, Locale locale) {
        String cacheKey = basename + locale.getCountry();
        if (cachedData.containsKey(cacheKey)) {
            return cachedData.get(cacheKey);
        }
        ResourceBundle bundle = getResourceBundle(basename, locale);
        TreeMap treeMap = new TreeMap();
        for (String key : bundle.keySet()) {
            treeMap.put(key, getMessage(key, null, locale));
        }
        cachedData.put(cacheKey, treeMap);
        return treeMap;
    }

    public Map getKeyValues(Locale locale) {
        String cacheKey = WHOLE + locale.getCountry();
        if (cachedData.containsKey(cacheKey)) {
            return cachedData.get(cacheKey);
        }
        TreeMap treeMap = new TreeMap();
        for (String baseName : baseNames) {
            treeMap.putAll(getKeyValues(baseName, locale));
        }
        cachedData.put(cacheKey, treeMap);
        return treeMap;
    }

    public void setBasenames(String[] basenames) {
        baseNames = CollectionUtils.arrayAsSet(basenames);
        super.setBasenames(basenames);
    }


}
1 голос
/ 24 мая 2018

Если вы не хотите расширять класс ResourceBundleMessageSource, вы можете сделать это следующим образом:

@Component
class FrontendMessageLoader {
  import scala.collection.JavaConverters._

  @Resource(name = "messageSource")
  var properties: ResourceBundleMessageSource = _

  @PostConstruct def init(): Unit = {
    val locale = new Locale("en", "US")
    val baseNames: mutable.Set[String] = properties.getBasenameSet.asScala
    val messageMap: Map[String, String] =
      baseNames.flatMap { baseName =>
        readMessages(baseName, locale)
      }.toMap
    }
  }

  private def readMessages(baseName: String, locale: Locale) = {
    val bundle = ResourceBundle.getBundle(baseName, locale)
    bundle.getKeys.asScala.map(key => key -> bundle.getString(key))
  }
}

Это Scala, но вы поняли идею и можете легко преобразовать ее в Java, если это необходимо. Дело в том, что вы получаете базовые имена (которые являются файлами свойств), а затем загружаете эти файлы, а затем вы можете получить ключи. Я передаю также локаль, но есть несколько перегруженных версий для ResourceBundle.getBundle, и вы можете выбрать ту, которая соответствует вашим потребностям.

1 голос
/ 27 октября 2011

Мое решение:

  1. использовать пружину <util:properties />

    <util:properties id="message" location="classpath:messages.properties" />
    
  2. Добавить перехватчик, для которого 'message' установлено для запроса атрибутов

    request.setAttribute("msgKeys", RequestContextUtils.getWebApplicationContext(request).getBean("message"));
    
  3. В JSP используйте msgKeys для извлечения сообщения через тег <fmt:message />

    var msg = {<c:forEach items="${msgKeys}" var="m" varStatus="s">
        <c:set var="key" value="${fn:substringBefore(m,'=')}"/>
        "${fn:substringAfter(key)}":"<fmt:message key="${key}"/>",
    </c:forEach>};
    

(Конечно, вам нужно дополнительно экранировать вывод, чтобы соответствовать строке js.)

Итак, для свойств

app.name=Application Name
app.title=Some title

будет выводить

var msg = { "name":"Application Name", "title":"Some title" };

Хорошо, что вы можете сделать больше логики в цикле c:forEach.

0 голосов
/ 03 августа 2011

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

{
   name: {label: "Nimi", value: "yay"},
   title: {label: "Ostikko", value: "hoopla"},
   group: {label: "Rhymä", value: "doo"},
}

Вы можете создать промежуточный объект (самый простой будет Map<String, Map<String, String>>), который содержит эти значения иДжексон будет сериализовать его в JSON.Я думаю, что это лучше, чем опция динамического Javascript-файла.Это решение также в конечном итоге делает то, что вы пытались сделать с динамическим Javascript-файлом, то есть связывает ключ с его разрешенным значением.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...