/* * Copyright (C) 2012 Red Hat, Inc. and/or its affiliates. * * 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.jboss.errai.ui.client.local.spi; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.logging.Logger; import javax.enterprise.context.ApplicationScoped; import org.jboss.errai.common.client.util.CreationalCallback; import org.jboss.errai.common.client.util.Properties; import org.jboss.errai.ioc.client.container.IOC; import org.jboss.errai.ioc.client.container.async.AsyncBeanDef; import org.jboss.errai.ui.shared.DomVisit; import org.jboss.errai.ui.shared.JSONMap; import org.jboss.errai.ui.shared.TranslationDomRevisitor; import org.jboss.errai.ui.shared.api.annotations.Templated; import org.jboss.errai.ui.shared.wrapper.ElementWrapper; import com.google.gwt.dom.client.Document; import com.google.gwt.user.client.ui.Composite; /** * A base class for a generated translation service that includes all of the translation visible at * compile time. * * @author eric.wittmann@redhat.com * @author Max Barkley <mbarkley@redhat.com> */ public abstract class TranslationService { private static final Logger logger = Logger.getLogger(TranslationService.class.getName()); private static String currentLocale = null; private final Dictionary dictionary = new Dictionary(); private static boolean shouldSearchKeyOnDefaultLocale = Boolean.parseBoolean(System.getProperty("errai.i18n.default_per_key")); /** * Constructor. */ public TranslationService() {} /** * @return true if the translation service is enabled/should be used */ public boolean isEnabled() { return !dictionary.getSupportedLocals().isEmpty(); } public Collection<String> getSupportedLocales() { return dictionary.getSupportedLocals(); } /** * Registers the bundle with the translation service. * * @param jsonData */ protected void registerJsonBundle(final String data, final String locale) { registerJSON(JSONMap.create(data), locale); } /** * Registers the bundle with the translation service. * * @param jsonData */ protected void registerPropertiesBundle(final String data, final String locale) { final Map<String, String> translation = Properties.load(data); for (final Entry<String, String> entry : translation.entrySet()) { registerTranslation(entry.getKey(), entry.getValue(), locale); } } /** * Registers a single translation. * * @param key * @param value * @param locale */ protected void registerTranslation(final String key, final String value, String locale) { if (locale != null) { locale = locale.toLowerCase(); } dictionary.put(locale, key, value); } /** * Registers some i18n data with the translation service. This is called for each discovered * bundle file. * * @param data * @param locale */ protected void registerJSON(final JSONMap data, final String locale) { logger.fine("Registering translation data for locale: " + locale); final Set<String> keys = data.keys(); for (final String key : keys) { final String value = data.get(key); registerTranslation(key, value, locale); } logger.fine("Registered " + keys.size() + " translation keys."); } /** * Gets the translation for the given i18n translation key. * * @param translationKey */ public String getTranslation(final String translationKey) { final String localeName = getActiveLocale(); return getTranslation(translationKey, localeName, null); } private String getTranslation(final String translationKey, final String localeName, final String defaultValue) { logger.fine("Translating key: " + translationKey + " into locale: " + localeName); final Map<String, String> translationData = dictionary.get(localeName); if (translationData.containsKey(translationKey)) { logger.fine("Translation found in locale map: " + localeName); return translationData.get(translationKey); } else { final String nonNamespacedKey = translationKey.substring(translationKey.indexOf('.')+1); if (!nonNamespacedKey.equals(translationKey) && translationData.containsKey(nonNamespacedKey)) { logger.fine("Global translation found in locale map: " + localeName); return translationData.get(nonNamespacedKey); } } if (localeName != null && shouldSearchKeyOnDefaultLocale) { // Nothing? Tries to find translation in default locale. logger.fine("Translation not found in locale map: " + localeName); return getTranslation(translationKey, null, defaultValue); } // Nothing in the default locale? Then return the default value. logger.fine("Translation not found in any locale map, leaving unchanged."); return defaultValue; } /** * Look up a message in the i18n resource message bundle by key, then format the message with the * given arguments and return the result. * * @param key * @param args */ public String format(final String key, final Object... args) { final String pattern = getTranslation(key, getActiveLocale(), "!!!" + key + "!!!"); //$NON-NLS-1$ //$NON-NLS-2$ if (args.length == 0) return pattern; // TODO add support for actually using { in a message final StringBuilder builder = new StringBuilder(pattern); int argId = 0; for (final Object arg : args) { final String rcode = "{" + (argId++) + "}"; final int startIdx = builder.indexOf(rcode); final int endIdx = startIdx + rcode.length(); builder.replace(startIdx, endIdx, String.valueOf(arg)); } return builder.toString(); } public String getActiveLocale() { final String localeName = currentLocale(); if (!dictionary.get(localeName).isEmpty()) { return localeName; } if (localeName != null && localeName.contains("_") && !dictionary.get(localeName.substring(0, localeName.indexOf('_'))).isEmpty()) { return localeName.substring(0, localeName.indexOf('_')); } return null; } /** * @return the currently configured locale */ public static String currentLocale() { if (currentLocale == null) { String locale = com.google.gwt.user.client.Window.Location.getParameter("locale"); if (locale == null || locale.trim().length() == 0) { locale = getBrowserLocale(); if (locale != null) { if (locale.indexOf('-') != -1) { locale = locale.replace('-', '_'); } } } if (locale == null) { locale = "default"; } currentLocale = locale.toLowerCase(); logger.fine("Discovered the current locale (either via query string or navigator) of: " + currentLocale); } return currentLocale; } /** * Gets the browser's configured locale. */ public final static native String getBrowserLocale() /*-{ if ($wnd.navigator.language) { return $wnd.navigator.language; } if ($wnd.navigator.userLanguage) { return $wnd.navigator.userLanguage; } if ($wnd.navigator.browserLanguage) { return $wnd.navigator.browserLanguage; } if ($wnd.navigator.systemLanguage) { return $wnd.navigator.systemLanguage; } return null; }-*/; /** * Forcibly set the current locale and re-translate all instantiated {@link Templated} beans. * * @param locale */ public final static void setCurrentLocale(final String locale) { setCurrentLocaleWithoutUpdate(locale); retranslateTemplatedBeans(); } /** * Forcibly set if the default locale should be searched when a translation key does not have a * translation registered. */ public static void setShouldSearchKeyOnDefaultLocale(final boolean newShouldSearchKeyOnDefaultLocale) { shouldSearchKeyOnDefaultLocale = newShouldSearchKeyOnDefaultLocale; } /** * Forcibly set the current locale but do not re-translate existing templated instances. Mostly * useful for testing. * * @param locale */ public final static void setCurrentLocaleWithoutUpdate(final String locale) { currentLocale = locale; } /** * Re-translate displayed {@link Templated} beans to the current locale. */ public static void retranslateTemplatedBeans() { // Translate DOM-attached templates DomVisit.revisit(new ElementWrapper(Document.get().getBody()), new TranslationDomRevisitor()); // Translate DOM-detached Singleton templates for (final AsyncBeanDef<Composite> beanDef : IOC.getAsyncBeanManager().lookupBeans(Composite.class)) { final Class<? extends Annotation> scope = beanDef.getScope(); if (scope != null && (scope.equals(ApplicationScoped.class))) beanDef.getInstance(new CreationalCallback<Composite>() { @Override public void callback(final Composite beanInstance) { /* * Only translate parent-less widgets to avoid re-translating a single widget multiple * times (the call to revisit will traverse the whole subtree rooted at this widget). */ if (beanInstance.getParent() == null) DomVisit.revisit(new ElementWrapper(beanInstance.getElement()), new TranslationDomRevisitor()); } }); } } }