com.github.jknack.handlebars.helper.I18nHelper Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/**
* Copyright (c) 2012-2015 Edgar Espina
*
* This file is part of Handlebars.java.
*
* 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 com.github.jknack.handlebars.helper;
import static org.apache.commons.lang3.StringUtils.defaultIfEmpty;
import static org.apache.commons.lang3.Validate.isTrue;
import static org.apache.commons.lang3.Validate.notEmpty;
import static org.apache.commons.lang3.Validate.notNull;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.Options;
import com.github.jknack.handlebars.internal.Locales;
/**
* Implementation of i18n helper for Java and JavaScript.
*
* The Java implementation use {@link ResourceBundle}.
*
*
* The JavaScript version use the I18n library.
* {@link ResourceBundle} are converted to JavaScript code.
*
*
* @author edgar.espina
* @since 0.9.0
* @see ResourceBundle
*/
public enum I18nHelper implements Helper {
/**
*
* A helper built on top of {@link ResourceBundle}. A {@link ResourceBundle} is the most well
* known mechanism for internationalization (i18n).
*
*
* messages.properties:
*
*
* hello=Hola
*
*
* Basic Usage:
*
*
* {{i18n "hello"}}
*
*
*
* Will result in: Hola
*
* Using a locale:
*
*
* {{i18n "hello" locale="es_AR"}}
*
*
* Using a different bundle:
*
*
* {{i18n "hello" bundle="myMessages"}}
*
*
* Using a message format:
*
*
* hello=Hola {0}!
*
*
*
* {{i18n "hello" "Handlebars.java"}}
*
*
* @author edgar.espina
* @since 0.9.0
* @see ResourceBundle
*/
i18n {
/**
*
* A helper built on top of {@link ResourceBundle}. A {@link ResourceBundle} is the most well
* known mechanism for internationalization (i18n).
*
*
*
messages.properties:
*
*
*
* hello=Hola
*
*
* Basic Usage:
*
*
* {{i18n "hello"}}
*
*
*
* Will result in: Hola
*
* Using a locale:
*
*
* {{i18n "hello" locale="es_AR"}}
*
*
* Using a different bundle:
*
*
* {{i18n "hello" bundle="myMessages"}}
*
*
* Using a message format
*
*
* hello=Hola {0}!
*
*
*
* {{i18n "hello" "Handlebars.java"}}
*
*
* @param key The bundle's key. Required.
* @param options The helper's options. Not null.
* @return An i18n message.
* @throws IOException If the bundle wasn't resolve.
*/
@Override
public Object apply(final String key, final Options options) throws IOException {
notEmpty(key, "found: '%s', expected 'bundle's key'", key);
Locale locale = Locales.fromString((String) options.hash("locale", defaultLocale.toString()));
String baseName = options.hash("bundle", defaultBundle);
ClassLoader classLoader = options.hash("classLoader", getClass().getClassLoader());
I18nSource localSource = source == null ? new DefI18nSource(charset, baseName, locale, classLoader) : source;
return localSource.message(key, locale, options.params);
}
}
,
/**
*
* Translate a {@link ResourceBundle} into JavaScript code. The generated code assume you added
* the I18n
*
*
* It converts message patterns like: Hi {0}
into Hi {{arg0}}
. This make
* possible to the I18n JS library to interpolate variables.
*
*
* Note: make sure you include I18n in your
* application. Otherwise, the generated code will fail.
*
*
* Usage:
*
*
*
* {{i18nJs locale?}}
*
*
* If locale argument is present it will translate that locale to JavaScript. Otherwise, the
* default locale.
*/
i18nJs {
/**
* The message format pattern.
*/
private final Pattern pattern = Pattern.compile("\\{(\\d+)\\}");
/**
*
* Translate a {@link ResourceBundle} into JavaScript code. The generated code assume you added
* the I18n
*
*
* It converts message patterns like: Hi {0}
into Hi {{arg0}}
. This
* make possible to the I18n JS library to interpolate variables.
*
*
* Note: make sure you include I18n in your
* application. Otherwise, the generated code will fail.
*
*
* Usage:
*
*
*
* {{i18nJs [locale] [bundle=messages] [wrap=true]}}
*
*
* If locale argument is present it will translate that locale to JavaScript. Otherwise, the
* default locale.
*
* Use wrap=true for wrapping the code with a script tag.
*
* @param localeName The locale's name. Optional.
* @param options The helper's options. Not null.
* @return JavaScript code from {@link ResourceBundle}.
* @throws IOException If bundle wasn't resolve.
*/
@Override
public Object apply(final String localeName, final Options options) throws IOException {
Locale locale = Locales.fromString(defaultIfEmpty(localeName, defaultLocale.toString()));
String baseName = options.hash("bundle", defaultBundle);
ClassLoader classLoader = options.hash("classLoader", getClass().getClassLoader());
I18nSource localSource = source == null ? new DefI18nSource(charset, baseName, locale, classLoader) : source;
StringBuilder buffer = new StringBuilder();
Boolean wrap = options.hash("wrap", true);
if (wrap) {
buffer.append("\n");
}
return new Handlebars.SafeString(buffer);
}
/**
* Convert expression {0}
into {{arg0}}
and escape EcmaScript
* characters.
*
* @param message The candidate message.
* @return A valid I18n message.
*/
private String message(final String message) {
CharSequence escapedMessage = Handlebars.Utils.escapeExpression(message);
Matcher matcher = pattern.matcher(escapedMessage);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(result, "{{arg" + matcher.group(1) + "}}");
}
matcher.appendTail(result);
return result.toString();
}
}
;
/**
* The default locale. Required.
*/
protected Locale defaultLocale = Locale.getDefault();
/**
* The default's bundle. Required.
*/
protected String defaultBundle = "messages";
/**
* The message source to use.
*/
protected I18nSource source;
/**
* Charset. *
*/
protected Charset charset = StandardCharsets.UTF_8;
/**
* Set the charset to use.
*
* NotThreadSafe Make sure to call this method ONCE at start time.
*
* @param charset Charset. Required.
*/
public void setCharset(final Charset charset) {
this.charset = notNull(charset, "Charset required.");
}
/**
* Set the message source.
*
* NotThreadSafe Make sure to call this method ONCE at start time.
*
* @param source The message source. Required.
*/
public void setSource(final I18nSource source) {
this.source = notNull(source, "The i18n source is required.");
}
/**
* Set the default bundle's name. Default is: messages and this method let you override the
* default bundle's name to something else.
*
* NotThreadSafe Make sure to call this method ONCE at start time.
*
* @param bundle The default's bundle name. Required.
*/
public void setDefaultBundle(final String bundle) {
this.defaultBundle = notEmpty(bundle, "A bundle's name is required.");
}
/**
* Set the default locale. Default is system dependent and this method let you override the
* default bundle's name to something else.
*
* NotThreadSafe Make sure to call this method ONCE at start time.
*
* @param locale The default locale name. Required.
*/
public void setDefaultLocale(final Locale locale) {
this.defaultLocale = notNull(locale, "A locale is required.");
}
}
/**
* Default implementation of I18nSource.
* @deprecated com.github.jknack.handlebars.helper package is deprecated and marked for removal in subsequent releases which will involve removal of the handlebars dependency in AEM.
*/
@Deprecated(since = "2024-07-10")
class DefI18nSource implements I18nSource {
/**
* UTF8 resource bundle control.
*
* Source: Source: https://stackoverflow.com/questions/4659929
*
* @author edgar
*
* @deprecated com.github.jknack.handlebars.helper package is deprecated and marked for removal in subsequent releases which will involve removal of the handlebars dependency in AEM.
*/
@Deprecated(since = "2024-07-10")
public static class UTF8Control extends ResourceBundle.Control {
/**
* Charset.
*/
private final Charset charset;
/**
* Creates a new utf8 control.
*
* @param charset Charset.
*/
UTF8Control(final Charset charset) {
this.charset = charset;
}
@Override
public ResourceBundle newBundle(final String baseName, final Locale locale, final String format, final ClassLoader loader, final boolean reload) throws IOException {
// The below is a copy of the default implementation.
String bundleName = toBundleName(baseName, locale);
String resourceName = toResourceName(bundleName, "properties");
InputStream stream = null;
try {
stream = loader.getResourceAsStream(resourceName);
PropertyResourceBundle bundle = new PropertyResourceBundle(new InputStreamReader(stream, charset));
return bundle;
} finally {
if (stream != null) {
stream.close();
}
}
}
}
/**
* The resource bundle.
*/
private ResourceBundle bundle;
/**
* Creates a new {@link DefI18nSource}.
*
* @param charset Charset to use.
* @param baseName The base name.
* @param locale The locale.
* @param classLoader The classloader.
*/
DefI18nSource(final Charset charset, final String baseName, final Locale locale, final ClassLoader classLoader) {
bundle = ResourceBundle.getBundle(baseName, locale, classLoader, new UTF8Control(charset));
}
@Override
public String[] keys(final String basename, final Locale locale) {
Enumeration keys = bundle.getKeys();
List result = new ArrayList<>();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
result.add(key);
}
return result.toArray(new String[result.size()]);
}
@Override
public String message(final String key, final Locale locale, final Object... args) {
isTrue(bundle.containsKey(key), "no message found: '%s' for locale '%s'.", key, locale);
String message = bundle.getString(key);
if (args.length == 0) {
return message;
}
MessageFormat format = new MessageFormat(message, locale);
return format.format(args);
}
}