【问题标题】:Email Internationalization using Velocity/FreeMarker Templates使用 Velocity/FreeMarker 模板的电子邮件国际化
【发布时间】:2012-03-25 05:42:11
【问题描述】:

如何使用 Velocity 或 FreeMarker 等模板引擎构建电子邮件正文来实现 i18n?

通常人们倾向于创建如下模板:

<h3>${message.hi} ${user.userName}, ${message.welcome}</h3>
<div>
   ${message.link}<a href="mailto:${user.emailAddress}">${user.emailAddress}</a>.
</div>

并创建具有以下属性的资源包:

message.hi=Hi
message.welcome=Welcome to Spring!
message.link=Click here to send email.

这会产生一个基本问题:如果我的 .vm 文件变得很大,包含许多文本行,那么在单独的资源包 (.properties) 文件中翻译和管理每个文件就会变得乏味。

我想做的是,为每种语言创建一个单独的 .vm 文件,例如 mytemplate_en_gb.vm, mytemplate_fr_fr.vm, mytemplate_de_de.vm,然后以某种方式告诉 Velocity/Spring 根据输入的语言环境选择正确的文件。

这在春天可能吗?还是应该寻找更简单、更明显的替代方法?

注意:我已经看过Spring tutorial 中关于如何使用模板引擎创建电子邮件正文的内容。但它似乎没有回答我关于 i18n 的问题。

【问题讨论】:

    标签: java spring internationalization template-engine


    【解决方案1】:

    事实证明,使用一个模板和多个 language.properties 文件胜过使用多个模板。

    这会产生一个基本问题:如果我的 .vm 文件变大 多行文本,翻译和管理每一行变得繁琐 它们在单独的资源包 (.properties) 文件中。

    如果您的电子邮件结构在多个 .vm 文件中重复,则更难维护。此外,还必须重新发明资源包的后备机制。资源包尝试在给定语言环境的情况下找到最接近的匹配项。例如,如果语言环境是en_GB,它会尝试按顺序查找以下文件,如果没有一个可用,则回退到最后一个。

    • language_en_GB.properties
    • language_en.properties
    • language.properties

    我将在此处发布(详细)我为简化阅读 Velocity 模板中的资源包而必须做的事情。

    在 Velocity 模板中访问资源包

    弹簧配置

    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="content/language" />
    </bean>
    
    <bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean">    
        <property name="resourceLoaderPath" value="/WEB-INF/template/" />
        <property name="velocityProperties">
            <map>
                <entry key="velocimacro.library" value="/path/to/macro.vm" />
            </map>
        </property>
    </bean>
    
    <bean id="templateHelper" class="com.foo.template.TemplateHelper">
        <property name="velocityEngine" ref="velocityEngine" />
        <property name="messageSource" ref="messageSource" />
    </bean>
    

    模板助手类

    public class TemplateHelper {
        private static final XLogger logger = XLoggerFactory.getXLogger(TemplateHelper.class);
        private MessageSource messageSource;
        private VelocityEngine velocityEngine;
    
        public String merge(String templateLocation, Map<String, Object> data, Locale locale) {
            logger.entry(templateLocation, data, locale);
    
            if (data == null) {
                data = new HashMap<String, Object>();
            }
    
            if (!data.containsKey("messages")) {
                data.put("messages", this.messageSource);
            }
    
            if (!data.containsKey("locale")) {
                data.put("locale", locale);
            }
    
            String text =
                VelocityEngineUtils.mergeTemplateIntoString(this.velocityEngine,
                    templateLocation, data);
    
            logger.exit(text);
    
            return text;
        }
    }
    

    速度模板

    #parse("init.vm")
    #msg("email.hello") ${user} / $user,
    #msgArgs("email.message", [${emailId}]).
    <h1>#msg("email.heading")</h1>
    

    我必须创建一个速记宏 msg 才能读取消息包。它看起来像这样:

    #**
     * msg
     *
     * Shorthand macro to retrieve locale sensitive message from language.properties
     *#
    #macro(msg $key)
    $messages.getMessage($key,null,$locale)
    #end
    
    #macro(msgArgs $key, $args)
    $messages.getMessage($key,$args.toArray(),$locale)
    #end
    

    资源包

    email.hello=Hello
    email.heading=This is a localised message
    email.message=your email id : {0} got updated in our system.
    

    用法

    Map<String, Object> data = new HashMap<String, Object>();
    data.put("user", "Adarsh");
    data.put("emailId", "adarsh@email.com");
    
    String body = templateHelper.merge("send-email.vm", data, locale);
    

    【讨论】:

    • 这是一篇非常有用的帖子,但是,如果能够将模板值传递给国际化消息,那就太好了:email.hello=hello {0}, #parse("init. vm") #msg("email.hello" $user.name) 目前这似乎不可能,但我不明白为什么。
    • @Ken 谢谢。你节省了我的时间。
    • 也许有人会遇到同样的问题:如果您必须向 getMessage 发送多个参数,您可以使用 $messages.getMessage($key, $parameters.toArray($arraySample), $locale)其中 $arraySample 已在您的数据映射中设置: data.put("arraySample", new Object[0]);我不知道这是否是最好的解决方案,但它可以解决问题,因为默认情况下,Velocity 将发送一个 ArrayList 而不是一个数组
    【解决方案2】:

    这是 Freemarker 的解决方案(一个模板,多个资源文件)。

    主程序

    // defined in the Spring configuration file
    MessageSource messageSource;
    
    Configuration config = new Configuration();
    // ... additional config settings
    
    // get the template (notice that there is no Locale involved here)
    Template template = config.getTemplate(templateName);
    
    Map<String, Object> model = new HashMap<String, Object>();
    // the method called "msg" will be available inside the Freemarker template
    // this is where the locale comes into play 
    model.put("msg", new MessageResolverMethod(messageSource, locale));
    

    MessageResolverMethod 类

    private class MessageResolverMethod implements TemplateMethodModel {
    
      private MessageSource messageSource;
      private Locale locale;
    
      public MessageResolverMethod(MessageSource messageSource, Locale locale) {
        this.messageSource = messageSource;
        this.locale = locale;
      }
    
      @Override
      public Object exec(List arguments) throws TemplateModelException {
        if (arguments.size() != 1) {
          throw new TemplateModelException("Wrong number of arguments");
        }
        String code = (String) arguments.get(0);
        if (code == null || code.isEmpty()) {
          throw new TemplateModelException("Invalid code value '" + code + "'");
        }
        return messageSource.getMessage(code, null, locale);
      }
    

    }

    Freemarker 模板

    ${msg("subject.title")}
    

    【讨论】:

    • 谢谢。如果你想在模型级别应用你的解决方案,在 @ControllerAdvice-d 类里面放: @Autowired private MessageResolverMethod mrm; @ModelAttribute("msg") public FormatDateTimeMethodModel formatDateTime() { return mrm; }
    • 哇,谢谢!终于用这段代码将我的模板减少到一个。
    • 请注意,TemplateMethodModel 现在已弃用,而建议使用 TemplateMethodModelEx。此外,我必须通过toString() 方法将参数转换为字符串,而不是使用显式(String) 转换。
    猜你喜欢
    • 2015-07-15
    • 1970-01-01
    • 1970-01-01
    • 2011-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-03
    • 1970-01-01
    相关资源
    最近更新 更多