/* * Copyright 2013 Martin Kouba * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.trimou.handlebars.i18n; import static org.trimou.handlebars.OptionsHashKeys.BASE_NAME; import static org.trimou.handlebars.OptionsHashKeys.FORMAT; import static org.trimou.handlebars.OptionsHashKeys.LOCALE; import java.text.MessageFormat; import java.util.Formatter; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.trimou.engine.locale.LocaleSupport; import org.trimou.exception.MustacheException; import org.trimou.exception.MustacheProblem; import org.trimou.handlebars.Options; import org.trimou.handlebars.OptionsHashKeys; import org.trimou.util.Arrays; import org.trimou.util.ImmutableSet; /** * <p> * First register the helper instance: * </p> * <code> * MustacheEngineBuilder.newBuilder().registerHelper("msg", new ResourceBundleHelper("messages")).build(); * </code> * * <p> * Than use the helper in the template: * </p> * <code> * {{msg "my.key"}} * </code> * * <p> * The key need not be a string literal: * </p> * <code> * {{msg foo.key}} * </code> * * <p> * You may also override the default baseName: * </p> * <code> * {{msg "my.key" baseName="messages"}} * </code> * * <p> * And also use message parameters and {@link Formatter} or * {@link MessageFormat}: * </p> * <code> * hello.key=Hello %s! * </code> * <p/> * <code> * {{msg "hello.key" "world" format="printf"}} * </code> * * <p> * See also {@link Format} for more info about formats. * </p> * * <p> * Since 1.7 a custom {@link Locale} can be set via options hash with * {@link OptionsHashKeys#LOCALE} key. See also * {@link LocaleAwareValueHelper#getLocale(Options)}. * </p> * * <code> * {{msg "key" locale="fr"}} * </code> * * @author Martin Kouba * @see LocaleSupport */ public class ResourceBundleHelper extends LocaleAwareValueHelper { private static final Logger LOGGER = LoggerFactory .getLogger(ResourceBundleHelper.class); private final String defaultBaseName; private final Format defaultFormat; /** * * @param defaultBaseName */ public ResourceBundleHelper(String defaultBaseName) { this(defaultBaseName, Format.PRINTF); } /** * * @param defaultBaseName * @param defaultFormat */ public ResourceBundleHelper(String defaultBaseName, Format defaultFormat) { this.defaultBaseName = defaultBaseName; this.defaultFormat = defaultFormat; } @Override public void execute(Options options) { String key = options.getParameters().get(0).toString(); Object baseName = options.getHash().get(BASE_NAME); ResourceBundle bundle = ResourceBundle.getBundle( baseName != null ? baseName.toString() : defaultBaseName, getLocale(options)); if (bundle.containsKey(key)) { Format format = getFormat(options.getHash()); String stringValue = bundle.getString(key); if (Format.NO_FORMAT.equals(format)) { append(options, stringValue); } else { Object[] formatParams = getFormatParams( options.getParameters()); try { if (Format.PRINTF.equals(format)) { append(options, String.format(bundle.getString(key), formatParams)); } else if (Format.MESSAGE.equals(format)) { append(options, MessageFormat .format(bundle.getString(key), formatParams)); } } catch (Exception e) { throw new MustacheException(MustacheProblem.RENDER_IO_ERROR, e); } } } } @Override protected Set<String> getSupportedHashKeys() { return ImmutableSet.of(FORMAT, BASE_NAME, LOCALE); } private Format getFormat(Map<String, Object> hash) { if (hash.isEmpty() || !hash.containsKey(FORMAT)) { return defaultFormat; } String customFormat = hash.get(FORMAT).toString(); Format format = Format.parse(customFormat); if (format == null) { LOGGER.warn( "Unsupported format specified: {}, using the default one: {}", customFormat, defaultFormat.getValue()); format = defaultFormat; } return format; } private Object[] getFormatParams(List<Object> params) { if (params.size() > 1) { return params.subList(1, params.size()).toArray(); } return Arrays.EMPTY_OBJECT_ARRAY; } /** * * @author Martin Kouba */ public enum Format { /** * @see Formatter */ PRINTF("printf"), /** * @see MessageFormat */ MESSAGE("message"), /** * No formatting. */ NO_FORMAT("none"),; Format(String value) { this.value = value; } private String value; public String getValue() { return value; } static Format parse(String value) { for (Format format : values()) { if (value.equals(format.value)) { return format; } } return null; } } }