Spring - 获取所有可解析的消息密钥

时间:2011-08-02 16:25:32

标签: java javascript ajax spring spring-mvc

我有一个使用Spring MVC的Web应用程序。我希望我的界面只包含一个页面,它通过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 个答案:

答案 0 :(得分:13)

好吧,我通过扩展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();
    }
}

现在我可以访问密钥,但除了必须指定消息源basename外,我还必须在我的控制器中执行一个丑陋的强制转换。呃,这是很多耦合。

Set<String> keys = 
  ((ExposedResourceBundleMessageSource)
  messageSource).getKeys("messages", locale);

答案 1 :(得分:4)

我已经解决了这个问题。


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


}

答案 2 :(得分:1)

我的解决方案:

  1. 使用spring <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>};
    
  4. (当然你需要进一步转义输出以匹配js字符串。)

    所以,对于属性

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

    会输出

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

    关于这一点的好处是你可以在c:forEach循环中做更多的逻辑。

答案 3 :(得分:1)

如果您不想扩展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有多个重载版本,您可以选择符合您需求的版本。

答案 4 :(得分:0)

我不认为返回本地化值是不好的做法。你可以从你的控制器返回这样的东西(所以你只需要在那里解决密钥):

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

您可以创建一个保存这些值的中间对象(最简单的Map<String, Map<String, String>>),杰克逊会将其序列化为JSON。我认为它比动态Javascript文件选项更好。此解决方案最终还是会使用动态Javascript文件执行您正在尝试的操作,即将密钥与其已解析的值相关联。