/** * Copyright (c) 2014-2017 by the respective copyright holders. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.eclipse.smarthome.core.internal.i18n; import java.io.File; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Locale; import java.util.ResourceBundle; import java.util.ResourceBundle.Control; import org.eclipse.smarthome.core.common.osgi.ResourceBundleClassLoader; import org.eclipse.smarthome.core.i18n.LocaleProvider; import org.osgi.framework.Bundle; /** * The {@link LanguageResourceBundleManager} class manages all available i18n resources for one * specific <i>OSGi</i> bundle. Any i18n resource is searched within the {@link RESOURCE_DIRECTORY} of the bundle and * <i>not</i> within the general bundle classpath. For the translation, the * i18n mechanism of Java ({@link ResourceBundle}) is used. * <p> * This implementation uses the user defined {@link ResourceBundleClassLoader} to map the bundle resource files to usual * URLs which the Java {@link ResourceBundle} can handle. * * @author Michael Grammling - Initial Contribution * @author Markus Rathgeb - Add locale provider support */ public class LanguageResourceBundleManager { /** The directory within the bundle where the resource files are searched. */ protected static final String RESOURCE_DIRECTORY = "/ESH-INF/i18n"; /** The file pattern to filter out resource files. */ private static final String RESOURCE_FILE_PATTERN = "*.properties"; private LocaleProvider localeProvider; private Bundle bundle; private ClassLoader resourceClassLoader; private List<String> resourceNames; public LanguageResourceBundleManager(LocaleProvider localeProvider, Bundle bundle) { if (bundle == null) { throw new IllegalArgumentException("The Bundle must not be null!"); } this.localeProvider = localeProvider; this.bundle = bundle; this.resourceClassLoader = new ResourceBundleClassLoader(bundle, RESOURCE_DIRECTORY, RESOURCE_FILE_PATTERN); this.resourceNames = determineResourceNames(); } public Bundle getBundle() { return this.bundle; } /** * Releases any cached resources which were managed by this class from the {@link ResourceBundle}. */ public void clearCache() { ResourceBundle.clearCache(this.resourceClassLoader); } /** * Returns {@code true} if the specified resource is managed by this instance * and therefore the according module is responsible for translations, * otherwise {@code false}. * * @param resource the resource to check (could be null or empty) * @return true if the specified resource is managed by this instance, otherwise false */ public boolean containsResource(String resource) { if (resource != null) { return this.resourceNames.contains(resource); } return false; } /** * Returns {@code true} if this instance and therefore the according module provides * resource information, otherwise {@code false}. * * @return true if the according bundle provides resource information, otherwise false */ public boolean containsResources() { return (this.resourceNames.size() > 0); } private List<String> determineResourceNames() { List<String> resourceNames = new ArrayList<String>(); Enumeration<URL> resourceFiles = this.bundle.findEntries(RESOURCE_DIRECTORY, RESOURCE_FILE_PATTERN, true); if (resourceFiles != null) { while (resourceFiles.hasMoreElements()) { URL resourceURL = resourceFiles.nextElement(); String resourcePath = resourceURL.getFile(); File resourceFile = new File(resourcePath); String resourceFileName = resourceFile.getName(); String baseName = resourceFileName.replaceFirst("[._]+.*", ""); if (!resourceNames.contains(baseName)) { resourceNames.add(baseName); } } } return resourceNames; } /** * Returns a translation for the specified key in the specified locale (language) by only * considering the specified resource section. The resource is equal to a base name and * therefore it is mapped to one translation package (all files which belong to the base * name). * <p> * If no translation could be found, {@code null} is returned. If the location is not specified, the default * location is used. * * @param resource the resource to be used for look-up (could be null or empty) * @param key the key to be translated (could be null or empty) * @param locale the locale (language) to be used (could be null) * * @return the translated text, or null if the key could not be translated */ public String getText(String resource, String key, Locale locale) { if ((key != null) && (!key.isEmpty())) { if (locale == null) { locale = localeProvider.getLocale(); } if (resource != null) { return getTranslatedText(resource, key, locale); } else { for (String resourceName : this.resourceNames) { String text = getTranslatedText(resourceName, key, locale); if (text != null) { return text; } } } } return null; } /** * Returns a translation for the specified key in the specified locale (language) * by considering all resources in the according bundle. * <p> * If no translation could be found, {@code null} is returned. If the location is not specified, the default * location is used. * * @param key the key to be translated (could be null or empty) * @param locale the locale (language) to be used (could be null) * * @return the translated text, or null if the key could not be translated */ public String getText(String key, Locale locale) { return getText(null, key, locale); } private String getTranslatedText(String resourceName, String key, Locale locale) { try { // Modify the search order so that the following applies: // 1.) baseName + "_" + language + "_" + country // 2.) baseName + "_" + language // 3.) baseName // 4.) null -> leads to a default text // Not using the default fallback strategy helps that not the default locale // search order is applied between 2.) and 3.). ResourceBundle resourceBundle = ResourceBundle.getBundle(resourceName, locale, this.resourceClassLoader, Control.getNoFallbackControl(Control.FORMAT_PROPERTIES)); if (resourceBundle != null) { return resourceBundle.getString(key); } } catch (Exception ex) { // nothing to do } return null; } }