/*
* $Id$
*
* Copyright (c) 2007 by Rodney Kinney, Brent Easton
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.i18n;
import java.awt.Component;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.UIManager;
import VASSAL.Info;
import VASSAL.build.module.gamepieceimage.StringEnumConfigurer;
import VASSAL.preferences.Prefs;
import VASSAL.tools.ErrorDialog;
public class Resources {
private static Resources instance;
/*
* Translation of VASSAL is handled by standard Java I18N tools.
*
* vassalBundle - Resource Bundle for the VASSAL player interface editorBundle - Resource Bundle for the Module Editor
*
* These are implemented as PropertyResourceBundles, normally to be found in the VASSAL jar file. VASSAL will search
* first in the VASSAL install directory for bundles, then follow the standard Java Class Path
*/
protected BundleHelper vassalBundle;
protected BundleHelper editorBundle;
private VassalPropertyClassLoader bundleLoader = new VassalPropertyClassLoader();
/** Preferences key for the user's Locale */
public static final String LOCALE_PREF_KEY = "Locale";
// Note: The Locale ctor takes the lower-case two-letter ISO language code.
protected final List<Locale> supportedLocales =
new ArrayList<Locale>(Arrays.asList(
Locale.ENGLISH,
Locale.GERMAN,
Locale.FRENCH,
Locale.ITALIAN,
new Locale("es"), // Spanish
Locale.JAPANESE,
new Locale("nl") // Dutch
)
);
protected Locale locale = Locale.getDefault();
protected static String DATE_FORMAT = "{0,date}";
private Resources() {
init();
}
private static Resources getInstance() {
synchronized (Resources.class) {
if (instance == null) {
instance = new Resources();
}
}
return instance;
}
private void init() {
Locale myLocale = Locale.getDefault();
ResourceBundle rb = ResourceBundle.getBundle("VASSAL.i18n.VASSAL", myLocale, bundleLoader);
// If the user has a resource bundle for their default language on their
// local machine, add it to the list of supported locales
if (rb.getLocale().getLanguage().equals(myLocale.getLanguage())) {
addLocale(myLocale);
}
final ArrayList<String> languages = new ArrayList<String>();
for (Locale l : supportedLocales) {
languages.add(l.getLanguage());
}
final Prefs p = Prefs.getGlobalPrefs();
final String savedLocale = p.getStoredValue(LOCALE_PREF_KEY);
if (savedLocale == null) {
myLocale = supportedLocales.iterator().next();
}
else {
myLocale = new Locale(savedLocale);
}
setInstanceLocale(myLocale);
final StringEnumConfigurer localeConfig = new StringEnumConfigurer(Resources.LOCALE_PREF_KEY, getInstanceString("Prefs.language"), languages.toArray(new String[languages.size()])) {
public Component getControls() {
if (box == null) {
final Component c = super.getControls();
box.setRenderer(new DefaultListCellRenderer() {
private static final long serialVersionUID = 1L;
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
JLabel l = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
l.setText(new Locale((String) value).getDisplayLanguage());
return l;
}
});
return c;
}
else {
return super.getControls();
}
}
};
localeConfig.setValue(locale.getLanguage());
p.addOption(getInstanceString("Prefs.general_tab"), localeConfig);
/*
new PreferencesEditor() {
public StringEnumEditor getEditor() {
return new StringEnumEditor(
prefs,
LOCALE,
getString("Prefs.language"),
);
}
};
*/
}
public static Collection<Locale> getSupportedLocales() {
return getInstance().supportedLocales;
}
public static void addSupportedLocale(Locale l) {
getInstance().addLocale(l);
}
private void addLocale(Locale l) {
l = new Locale(l.getLanguage());
if (!supportedLocales.contains(l)) {
supportedLocales.add(0, l);
StringEnumConfigurer config = (StringEnumConfigurer) Prefs
.getGlobalPrefs().getOption(LOCALE_PREF_KEY);
if (config != null) {
ArrayList<String> valid = new ArrayList<String>(Arrays
.asList(config.getValidValues()));
valid.add(0, l.getLanguage());
config.setValidValues(valid.toArray(new String[valid.size()]));
}
}
}
public static Collection<String> getVassalKeys() {
return Collections.list(getInstance().vassalBundle.getResourceBundle().getKeys());
}
public Collection<String> getEditorKeys() {
return Collections.list(editorBundle.getResourceBundle().getKeys());
}
/*
* Translation of individual modules is handled differently.
* There may be multiple Module.properties file active -
* Potentially one in the module plus one in each Extension loaded.
* These will be read into UberProperties structures
* with each file loaded supplying defaults for subsequent files.
*/
protected static final String MODULE_BUNDLE = "Module"; //$NON-NLS-1$
/*
* Commonly used i18n keys used in multiple components. By defining them
* centrally, they will only have to be translated once. Reference to these
* string should be made as follows:
*
* Resources.getString(Resources.VASSAL)
*/
public static final String VASSAL = "General.VASSAL"; //$NON-NLS-1$
public static final String ADD = "General.add"; //$NON-NLS-1$
public static final String REMOVE = "General.remove"; //$NON-NLS-1$
public static final String INSERT = "General.insert"; //$NON-NLS-1$
public static final String YES = "General.yes"; //$NON-NLS-1$
public static final String NO = "General.no"; //$NON-NLS-1$
public static final String CANCEL = "General.cancel"; //$NON-NLS-1$
public static final String SAVE = "General.save"; //$NON-NLS-1$
public static final String OK = "General.ok"; //$NON-NLS-1$
public static final String MENU = "General.menu"; //$NON-NLS-1$
public static final String LOAD = "General.load"; //$NON-NLS-1$
public static final String QUIT = "General.quit"; //$NON-NLS-1$
public static final String EDIT = "General.edit"; //$NON-NLS-1$
public static final String NEW = "General.new"; //$NON-NLS-1$
public static final String FILE = "General.file"; //$NON-NLS-1$
public static final String TOOLS = "General.tools"; //$NON-NLS-1$
public static final String HELP = "General.help"; //$NON-NLS-1$
public static final String CLOSE = "General.close"; //$NON-NLS-1$
public static final String DATE_DISPLAY = "General.date_display"; //$NON-NLS-1$
public static final String NEXT = "General.next"; //$NON-NLS-1$
public static final String REFRESH = "General.refresh"; //$NON-NLS-1$
public static final String SELECT = "General.select"; //$NON-NLS-1$
/*
* All i18n keys for the Module Editor must commence with "Editor."
* This allows us to use a single Resources.getString() call for both
* resource bundles.
*/
public static final String EDITOR_PREFIX = "Editor."; //$NON-NLS-1$
/*
* Common Editor labels that appear in many components.
*/
public static final String BUTTON_TEXT = "Editor.button_text_label"; //$NON-NLS-1$
public static final String TOOLTIP_TEXT = "Editor.tooltip_text_label"; //$NON-NLS-1$
public static final String BUTTON_ICON = "Editor.button_icon_label"; //$NON-NLS-1$
public static final String HOTKEY_LABEL = "Editor.hotkey_label"; //$NON-NLS-1$
public static final String COLOR_LABEL = "Editor.color_label"; //$NON-NLS-1$
public static final String NAME_LABEL = "Editor.name_label"; //$NON-NLS-1$
public static final String DESCRIPTION = "Editor.description_label"; //$NON-NLS-1$
/**
* Localize a user interface String.
*
* @param id
* String Id
* @return Localized result
*/
public static String getString(String id) {
return getInstance().getInstanceString(id);
}
private String getInstanceString(String id) {
return getBundleForKey(id).getString(id);
}
protected BundleHelper getBundleForKey(String id) {
return id.startsWith(EDITOR_PREFIX) ? getEditorBundle() : getVassalBundle();
}
protected BundleHelper getEditorBundle() {
if (editorBundle == null) {
editorBundle = new BundleHelper(ResourceBundle.getBundle("VASSAL.i18n.Editor", locale, bundleLoader));
}
return editorBundle;
}
protected BundleHelper getVassalBundle() {
if (vassalBundle == null) {
vassalBundle = new BundleHelper(ResourceBundle.getBundle("VASSAL.i18n.VASSAL", locale, bundleLoader));
}
return vassalBundle;
}
/**
* Localize a VASSAL user interface string
*
* @param id
* String id
* @return Localized result
*/
@Deprecated
public static String getVassalString(String id) {
return getInstance().getVassalBundle().getString(id);
}
/**
* Localize a VASSAL Module Editor String
*
* @param id
* String Id
* @return Localized Result
*/
@Deprecated
public static String getEditorString(String id) {
return getInstance().getEditorBundle().getString(id);
}
/**
* Localize a string using the supplied resource bundle
*
* @param bundle
* Resource bundle
* @param id
* String Id
* @return Localized result
*/
@Deprecated
public static String getString(ResourceBundle bundle, String id) {
String s = null;
try {
s = bundle.getString(id);
}
catch (Exception ex) {
System.err.println("No Translation: " + id);
}
// 2. Worst case, return the key
if (s == null) {
s = id;
}
return s;
}
public static String getString(String id, Object... params) {
return getInstance().getBundleForKey(id).getString(id, params);
}
protected static final String BASE_BUNDLE = "VASSAL.properties";
protected static final String EN_BUNDLE = "VASSAL_en.properties";
/**
* Custom Class Loader for loading VASSAL property files.
* Check first for files in the VASSAL home directory.
*
* @author Brent Easton
*/
public class VassalPropertyClassLoader extends ClassLoader {
public URL getResource(String name) {
URL url = getAResource(name);
// If no English translation of Vassal is available (as will usually be the case),
// drop back to the Base bundle.
if (url == null && name.endsWith(EN_BUNDLE)) {
url = getAResource(name.substring(0, name.lastIndexOf('/')+1)+BASE_BUNDLE);
}
return url;
}
public URL getAResource(String name) {
URL url = null;
final String propFileName = name.substring(name.lastIndexOf('/') + 1);
final File propFile = new File(Info.getHomeDir(), propFileName);
if (propFile.exists()) {
try {
url = propFile.toURI().toURL();
}
catch (MalformedURLException e) {
ErrorDialog.bug(e);
}
}
// No openable file in home dir, so let Java find one for us in
// the standard classpath.
if (url == null) {
url = this.getClass().getClassLoader().getResource(name);
}
return url;
}
}
public static void setLocale(Locale l) {
getInstance().setInstanceLocale(l);
}
private void setInstanceLocale(Locale l) {
locale = l;
editorBundle = null;
vassalBundle = null;
UIManager.put("OptionPane.yesButtonText", getInstanceString(YES)); //$NON-NLS-1$
UIManager.put("OptionPane.cancelButtonText", getInstanceString(CANCEL)); //$NON-NLS-1$
UIManager.put("OptionPane.noButtonText", getInstanceString(NO)); //$NON-NLS-1$
UIManager.put("OptionPane.okButtonText", getInstanceString(OK)); //$NON-NLS-1$
}
public static Locale getLocale() {
return getInstance().locale;
}
/**
* Return a standard formatted localised date
* @param date date to format
* @return formatted localized date
*/
public static String formatDate(Date date) {
return new MessageFormat(DATE_FORMAT).format(new Object[] {date});
}
}