/*
* Copyright (C) 2013 Google Inc.
*
* 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 interactivespaces.util.i18n;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import interactivespaces.InteractiveSpacesException;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.MessageFormat;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.Set;
/**
* Use the Java ResourceBundle API to provide internationalization services.
*
* <p>
* Java resource bundles for internationalization use files with a particular
* naming convention. The overall bundle has a prefix name giving the name of
* the collection, e.g. {@code messages}. The extension will be
* {@code .properties}. Next, the language code is used after the base. So, for
* example {@code messages_fr.properties} will give the properties in French.
*
* <p>
* If you need country specific modifications, you then add the country code.
* For example, for France, the file would be {@code messages_fr_FR.properties}.
*
* <p>
* There will be one of these files for each language, all contained in the same
* folder.
*
* <p>
* A file with the name {@code messages.properties} will give default values
* when a particular language cannot be found.
*
* @author Keith M. Hughes
*/
public class ResourceBundleI18nProvider implements I18nProvider {
/**
* The file extension used for the bundle files.
*/
private static final String BUNDLE_FILE_EXTENSION = "properties";
/**
* base folder for finding the message bundles.
*/
private File baseFolder;
/**
* base name for the bundle information.
*/
private String baseName;
/**
* The bundle control used to get the bundles from the file system.
*/
private ResourceBundle.Control bundleControl;
/**
* Construct a new internationalization provider
*
* @param baseFolder
* the folder that contains the internationalization files
* @param baseName
* the base name for the group of files providing the
* internationalized set of strings
*/
public ResourceBundleI18nProvider(final File baseFolder, String baseName) {
this.baseFolder = baseFolder;
this.baseName = baseName;
bundleControl = new ResourceBundle.Control() {
@Override
public List<String> getFormats(String baseName) {
if (baseName == null)
throw new NullPointerException();
return Lists.newArrayList(BUNDLE_FILE_EXTENSION);
}
@Override
public ResourceBundle newBundle(String baseName, Locale locale, String format,
ClassLoader loader, boolean reload) throws IllegalAccessException,
InstantiationException, IOException {
if (baseName == null || locale == null || format == null || loader == null)
throw new NullPointerException();
ResourceBundle bundle = null;
if (format.equals(BUNDLE_FILE_EXTENSION)) {
String bundleName = toBundleName(baseName, locale);
String resourceName = toResourceName(bundleName, format);
File file = new File(baseFolder, resourceName);
InputStream stream = null;
if (reload) {
if (file.exists()) {
stream = new FileInputStream(file);
}
} else {
stream = new FileInputStream(file);
}
if (stream != null) {
InputStreamReader reader =
new InputStreamReader(new BufferedInputStream(stream), "UTF-8");
bundle = new PropertyResourceBundle(reader);
reader.close();
}
}
return bundle;
}
};
}
@Override
public Set<Locale> getSupportedLocales() {
Set<Locale> locales = Sets.newHashSet();
// Only take the ones with the _ as these are the locale specific
// entries.
final String prefix = baseName + "_";
for (File messageFile : baseFolder.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith(prefix) && name.endsWith("." + BUNDLE_FILE_EXTENSION);
}
})) {
int beginIndex = baseName.length() + 1;
String fileName = messageFile.getName();
String languageCode = new String(fileName.substring(beginIndex, beginIndex + 2));
// Is there a country code?
String countryCode = null;
if (fileName.indexOf("_", beginIndex + 2) != -1) {
countryCode = new String(fileName.substring(beginIndex + 3, beginIndex + 5));
locales.add(new Locale(languageCode, countryCode));
} else {
locales.add(new Locale(languageCode));
}
}
return locales;
}
@Override
public Set<I18nSource> getAllSupportedSources() {
Set<I18nSource> sources = Sets.newHashSet();
for (Locale locale : getSupportedLocales()) {
sources.add(getSource(locale));
}
return sources;
}
@Override
public I18nSource getSource(Locale locale) {
try {
return new JavaI18nSource(ResourceBundle.getBundle(baseName, locale, bundleControl), locale);
} catch (MissingResourceException e) {
throw new InteractiveSpacesException(String.format(
"Could not find any internationalization resources for %s in %s", baseName,
baseFolder.getAbsolutePath()));
}
}
/**
* Internationalization resource using a MessageBundle.
*
* @author Keith M. Hughes
*/
private static class JavaI18nSource implements I18nSource {
/**
* Resource bundle containing the data.
*/
private ResourceBundle bundle;
/**
* The locale for this source.
*/
private Locale locale;
/**
* The message formatter for this source.
*/
private MessageFormat formatter;
public JavaI18nSource(ResourceBundle bundle, Locale locale) {
this.bundle = bundle;
this.locale = locale;
formatter = new MessageFormat("");
formatter.setLocale(locale);
}
@Override
public String getMessage(String key) {
try {
return bundle.getString(key);
} catch (MissingResourceException e) {
return null;
}
}
@Override
public String getMessage(String messageKey, List<String> args) {
String message = getMessage(messageKey);
if (message != null) {
formatter.applyPattern(message);
return formatter.format(args.toArray());
} else {
return null;
}
}
@Override
public Locale getLocale() {
return locale;
}
}
}