Make I18n class non-static.
[jkeytool.git] / src / net / pterodactylus / util / i18n / I18n.java
1 /*
2  * jSite2 - I18n.java -
3  * Copyright © 2008 David Roden
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 package net.pterodactylus.util.i18n;
21
22 import java.awt.event.InputEvent;
23 import java.awt.event.KeyEvent;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.lang.reflect.Field;
27 import java.text.MessageFormat;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.Locale;
31 import java.util.MissingResourceException;
32 import java.util.Properties;
33 import java.util.StringTokenizer;
34 import java.util.logging.Level;
35 import java.util.logging.Logger;
36
37 import javax.swing.KeyStroke;
38
39 import net.pterodactylus.util.logging.Logging;
40 import de.ina.util.io.Closer;
41
42 /**
43  * Class that handles i18n.
44  *
45  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
46  */
47 public class I18n {
48
49         /** Logger. */
50         private static final Logger logger = Logging.getLogger(I18n.class.getName());
51
52         /** The base name of the resource files. */
53         private String resourceName;
54
55         /** The class whose class loader is used to load resource files. */
56         private final Class<?> resourceClass;
57
58         /** List of I18nables that are notified when the language changes. */
59         private final List<I18nable> i18nables = new ArrayList<I18nable>();
60
61         /** The current locale. */
62         private Locale currentLocale;
63
64         /** The default language. */
65         private Properties defaultLanguage;
66
67         /** The current language. */
68         private Properties currentLanguage;
69
70         /**
71          * Creates a new I18n container.
72          *
73          * @param resourceName
74          *            The base name of the language resource files
75          * @param resourceClass
76          *            The class whose class loader is used to load resource files
77          */
78         public I18n(String resourceName, Class<?> resourceClass) {
79                 this.resourceName = resourceName;
80                 this.resourceClass = resourceClass;
81                 defaultLanguage = new Properties();
82                 InputStream inputStream = null;
83                 try {
84                         inputStream = resourceClass.getResourceAsStream(resourceName + ".properties");
85                         if (inputStream != null) {
86                                 defaultLanguage.load(inputStream);
87                         }
88                 } catch (IOException e) {
89                         /* something is fucked. */
90                 }
91                 setLocale(Locale.getDefault(), false);
92         }
93
94         /**
95          * Returns the translated value for a key. The translated values may contain
96          * placeholders that are replaced with the given parameters.
97          *
98          * @see MessageFormat
99          * @param key
100          *            The key to get
101          * @param parameters
102          *            The parameters in case the translated value contains
103          *            placeholders
104          * @return The translated message, or the key itself if no translation could
105          *         be found
106          */
107         public String get(String key, Object... parameters) {
108                 String value = null;
109                 value = currentLanguage.getProperty(key);
110                 if (value == null) {
111                         logger.log(Level.WARNING, "please fix “" + key + "”!", new Throwable());
112                         /* TODO - replace with value when done! */
113                         return null;
114                 }
115                 if ((parameters != null) && (parameters.length > 0)) {
116                         return MessageFormat.format(value, parameters);
117                 }
118                 return value;
119         }
120
121         /**
122          * Returns the keycode from the value of the given key. You can specify the
123          * constants in {@link KeyEvent} in the properties file, e.g. VK_S for the
124          * keycode ‘s’ when used for mnemonics.
125          *
126          * @param key
127          *            The key under which the keycode is stored
128          * @return The keycode
129          */
130         public int getKey(String key) {
131                 String value = currentLanguage.getProperty(key);
132                 if ((value != null) && value.startsWith("VK_")) {
133                         try {
134                                 Field field = KeyEvent.class.getField(value);
135                                 return field.getInt(null);
136                         } catch (SecurityException e) {
137                                 /* ignore. */
138                         } catch (NoSuchFieldException e) {
139                                 /* ignore. */
140                         } catch (IllegalArgumentException e) {
141                                 /* ignore. */
142                         } catch (IllegalAccessException e) {
143                                 /* ignore. */
144                         }
145                 }
146                 System.err.println("please fix “" + key + "”!");
147                 return KeyEvent.VK_UNDEFINED;
148         }
149
150         /**
151          * Returns a key stroke for use with swing accelerators.
152          *
153          * @param key
154          *            The key of the key stroke
155          * @return The key stroke, or <code>null</code> if no key stroke could be
156          *         created from the translated value
157          */
158         public KeyStroke getKeyStroke(String key) {
159                 String value = currentLanguage.getProperty(key);
160                 if (value == null) {
161                         return null;
162                 }
163                 StringTokenizer keyTokens = new StringTokenizer(value, "+- ");
164                 int modifierMask = 0;
165                 while (keyTokens.hasMoreTokens()) {
166                         String keyToken = keyTokens.nextToken();
167                         if ("ctrl".equalsIgnoreCase(keyToken)) {
168                                 modifierMask |= InputEvent.CTRL_DOWN_MASK;
169                         } else if ("alt".equalsIgnoreCase(keyToken)) {
170                                 modifierMask |= InputEvent.ALT_DOWN_MASK;
171                         } else if ("shift".equalsIgnoreCase(keyToken)) {
172                                 modifierMask |= InputEvent.SHIFT_DOWN_MASK;
173                         } else {
174                                 if (keyToken.startsWith("VK_")) {
175                                         if (keyToken.equals("VK_UNDEFINED")) {
176                                                 return null;
177                                         }
178                                         try {
179                                                 Field field = KeyEvent.class.getField(keyToken);
180                                                 return KeyStroke.getKeyStroke(field.getInt(null), modifierMask);
181                                         } catch (SecurityException e) {
182                                                 /* ignore. */
183                                         } catch (NoSuchFieldException e) {
184                                                 /* ignore. */
185                                         } catch (IllegalArgumentException e) {
186                                                 /* ignore. */
187                                         } catch (IllegalAccessException e) {
188                                                 /* ignore. */
189                                         }
190                                 }
191                                 return KeyStroke.getKeyStroke(keyToken.charAt(0), modifierMask);
192                         }
193                 }
194                 return null;
195         }
196
197         /**
198          * Sets the current locale.
199          *
200          * @param newLocale
201          *            The new locale to use
202          */
203         public void setLocale(Locale newLocale) {
204                 setLocale(newLocale, true);
205         }
206
207         /**
208          * Sets the current locale.
209          *
210          * @param newLocale
211          *            The new locale to use
212          * @param notify
213          *            <code>true</code> to notify registered {@link I18nable}s after
214          *            the language was changed
215          */
216         private void setLocale(Locale newLocale, boolean notify) {
217                 currentLocale = newLocale;
218                 InputStream inputStream = null;
219                 try {
220                         currentLanguage = new Properties(defaultLanguage);
221                         if (newLocale == Locale.ENGLISH) {
222                                 if (notify) {
223                                         notifyI18nables();
224                                 }
225                                 return;
226                         }
227                         inputStream = resourceClass.getResourceAsStream(resourceName + "_" + newLocale.getLanguage() + ".properties");
228                         if (inputStream != null) {
229                                 currentLanguage.load(inputStream);
230                                 if (notify) {
231                                         notifyI18nables();
232                                 }
233                         }
234                 } catch (MissingResourceException mre1) {
235                         currentLocale = Locale.ENGLISH;
236                 } catch (IOException ioe1) {
237                         currentLocale = Locale.ENGLISH;
238                 } finally {
239                         Closer.close(inputStream);
240                 }
241         }
242
243         /**
244          * Returns the current locale.
245          *
246          * @return The current locale
247          */
248         public Locale getLocale() {
249                 return currentLocale;
250         }
251
252         /**
253          * Finds all available locales.
254          *
255          * @return All available locales
256          */
257         public static List<Locale> findAvailableLanguages() {
258                 List<Locale> availableLanguages = new ArrayList<Locale>();
259                 availableLanguages.add(Locale.ENGLISH);
260                 availableLanguages.add(Locale.GERMAN);
261                 return availableLanguages;
262         }
263
264         /**
265          * Registers the given I18nable to be updated when the language is changed.
266          *
267          * @param i18nable
268          *            The i18nable to register
269          */
270         public void registerI18nable(I18nable i18nable) {
271                 i18nables.add(i18nable);
272         }
273
274         /**
275          * Deregisters the given I18nable to be updated when the language is
276          * changed.
277          *
278          * @param i18nable
279          *            The i18nable to register
280          */
281         public void deregisterI18nable(I18nable i18nable) {
282                 i18nables.remove(i18nable);
283         }
284
285         //
286         // PRIVATE METHODS
287         //
288
289         /**
290          * Notifies all registered {@link I18nable}s that the language was changed.
291          */
292         private void notifyI18nables() {
293                 for (I18nable i18nable : i18nables) {
294                         i18nable.updateI18n(this);
295                 }
296         }
297
298 }