Email Internationalization using Velocity/FreeMarker Templates

后端 未结 2 1016
不思量自难忘°
不思量自难忘° 2021-01-30 13:51

How can I achieve i18n using a templating engine such as Velocity or FreeMarker for constructing email body?

Typically people tend to create templates like:



        
相关标签:
2条回答
  • 2021-01-30 14:21

    It turns out using one template and multiple language.properties files wins over having multiple templates.

    This creates one basic problem: If my .vm files becomes large with many lines of text, it becomes tedious to translate and manage each of them in separate resource bundle (.properties) files.

    It is even harder to maintain if your email structure is duplicated over multiple .vm files. Also, one will have to re-invent the fall-back mechanism of resource bundles. Resource bundles try to find the nearest match given a locale. For example, if the locale is en_GB, it tries to find the below files in order, falling back to the last one if none of them is available.

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

    I will post (in detail) what I had to do to simplify reading resource bundles in Velocity templates here.

    Accessing Resource Bundle in a Velocity template

    Spring Configuration

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

    TemplateHelper Class

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

    Velocity Template

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

    I had to create a short-hand macro, msg in order to read from message bundles. It looks like this:

    #**
     * 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
    

    Resource Bundle

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

    Usage

    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);
    
    0 讨论(0)
  • 2021-01-30 14:24

    Here's the solution (one template, several resource files) for Freemarker.

    the main program

    // 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 class

    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 template

    ${msg("subject.title")}
    
    0 讨论(0)
提交回复
热议问题