output errors to System.err
[jSite2.git] / src / net / pterodactylus / jsite / 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.jsite.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
35 import javax.swing.KeyStroke;
36
37 import net.pterodactylus.util.io.Closer;
38
39 /**
40  * Class that handles i18n.
41  * 
42  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
43  * @version $Id$
44  */
45 public class I18n {
46
47         /** List of I18nables that are notified when the language changes. */
48         private static final List<I18nable> i18nables = new ArrayList<I18nable>();
49
50         /** The current locale. */
51         private static Locale currentLocale;
52
53         /** The default language. */
54         private static Properties defaultLanguage;
55
56         /** The current language. */
57         private static Properties currentLanguage;
58
59         static {
60                 defaultLanguage = new Properties();
61                 InputStream inputStream = null;
62                 try {
63                         inputStream = I18n.class.getResourceAsStream("jSite.properties");
64                         if (inputStream != null) {
65                                 defaultLanguage.load(inputStream);
66                         }
67                 } catch (IOException e) {
68                         /* something is fucked. */
69                 }
70                 setLocale(Locale.getDefault(), false);
71         }
72
73         /**
74          * Returns the translated value for a key. The translated values may contain
75          * placeholders that are replaced with the given parameters.
76          * 
77          * @see MessageFormat
78          * @param key
79          *            The key to get
80          * @param parameters
81          *            The parameters in case the translated value contains
82          *            placeholders
83          * @return The translated message, or the key itself if no translation could
84          *         be found
85          */
86         public static String get(String key, Object... parameters) {
87                 String value = null;
88                 value = currentLanguage.getProperty(key);
89                 if (value == null) {
90                         System.err.println("please fix “" + key + "”!");
91                         return null;
92                 }
93                 if ((parameters != null) && (parameters.length > 0)) {
94                         return MessageFormat.format(value, parameters);
95                 }
96                 return value;
97         }
98
99         /**
100          * Returns the keycode from the value of the given key. You can specify the
101          * constants in {@link KeyEvent} in the properties file, e.g. VK_S for the
102          * keycode ‘s’ when used for mnemonics.
103          * 
104          * @param key
105          *            The key under which the keycode is stored
106          * @return The keycode
107          */
108         public static int getKey(String key) {
109                 String value = currentLanguage.getProperty(key);
110                 if ((value != null) && value.startsWith("VK_")) {
111                         try {
112                                 Field field = KeyEvent.class.getField(value);
113                                 return field.getInt(null);
114                         } catch (SecurityException e) {
115                                 /* ignore. */
116                         } catch (NoSuchFieldException e) {
117                                 /* ignore. */
118                         } catch (IllegalArgumentException e) {
119                                 /* ignore. */
120                         } catch (IllegalAccessException e) {
121                                 /* ignore. */
122                         }
123                 }
124                 System.err.println("please fix “" + key + "”!");
125                 return KeyEvent.VK_UNDEFINED;
126         }
127
128         /**
129          * Returns a key stroke for use with swing accelerators.
130          * 
131          * @param key
132          *            The key of the key stroke
133          * @return The key stroke, or <code>null</code> if no key stroke could be
134          *         created from the translated value
135          */
136         public static KeyStroke getKeyStroke(String key) {
137                 String value = currentLanguage.getProperty(key);
138                 if (value == null) {
139                         return null;
140                 }
141                 StringTokenizer keyTokens = new StringTokenizer(value, "+- ");
142                 int modifierMask = 0;
143                 while (keyTokens.hasMoreTokens()) {
144                         String keyToken = keyTokens.nextToken();
145                         if ("ctrl".equalsIgnoreCase(keyToken)) {
146                                 modifierMask |= InputEvent.CTRL_DOWN_MASK;
147                         } else if ("alt".equalsIgnoreCase(keyToken)) {
148                                 modifierMask |= InputEvent.ALT_DOWN_MASK;
149                         } else if ("shift".equalsIgnoreCase(keyToken)) {
150                                 modifierMask |= InputEvent.SHIFT_DOWN_MASK;
151                         } else {
152                                 if (keyToken.startsWith("VK_")) {
153                                         try {
154                                                 Field field = KeyEvent.class.getField(keyToken);
155                                                 return KeyStroke.getKeyStroke(field.getInt(null), modifierMask);
156                                         } catch (SecurityException e) {
157                                                 /* ignore. */
158                                         } catch (NoSuchFieldException e) {
159                                                 /* ignore. */
160                                         } catch (IllegalArgumentException e) {
161                                                 /* ignore. */
162                                         } catch (IllegalAccessException e) {
163                                                 /* ignore. */
164                                         }
165                                 }
166                                 return KeyStroke.getKeyStroke(keyToken.charAt(0), modifierMask);
167                         }
168                 }
169                 return null;
170         }
171
172         /**
173          * Sets the current locale.
174          * 
175          * @param newLocale
176          *            The new locale to use
177          */
178         public static void setLocale(Locale newLocale) {
179                 setLocale(newLocale, true);
180         }
181
182         /**
183          * Sets the current locale.
184          * 
185          * @param newLocale
186          *            The new locale to use
187          * @param notify
188          *            <code>true</code> to notify registered {@link I18nable}s
189          *            after the language was changed
190          */
191         private static void setLocale(Locale newLocale, boolean notify) {
192                 currentLocale = newLocale;
193                 InputStream inputStream = null;
194                 try {
195                         currentLanguage = new Properties(defaultLanguage);
196                         if (newLocale == Locale.ENGLISH) {
197                                 if (notify) {
198                                         notifyI18nables();
199                                 }
200                                 return;
201                         }
202                         inputStream = I18n.class.getResourceAsStream("jSite_" + newLocale.getLanguage() + ".properties");
203                         if (inputStream != null) {
204                                 currentLanguage.load(inputStream);
205                                 if (notify) {
206                                         notifyI18nables();
207                                 }
208                         }
209                 } catch (MissingResourceException mre1) {
210                         currentLocale = Locale.ENGLISH;
211                 } catch (IOException ioe1) {
212                         currentLocale = Locale.ENGLISH;
213                 } finally {
214                         Closer.close(inputStream);
215                 }
216         }
217
218         /**
219          * Returns the current locale.
220          * 
221          * @return The current locale
222          */
223         public static Locale getLocale() {
224                 return currentLocale;
225         }
226
227         /**
228          * Finds all available locales.
229          * 
230          * @return All available locales
231          */
232         public static List<Locale> findAvailableLanguages() {
233                 List<Locale> availableLanguages = new ArrayList<Locale>();
234                 availableLanguages.add(Locale.ENGLISH);
235                 availableLanguages.add(Locale.GERMAN);
236                 return availableLanguages;
237         }
238
239         /**
240          * Registers the given I18nable to be updated when the language is changed.
241          * 
242          * @param i18nable
243          *            The i18nable to register
244          */
245         public static void registerI18nable(I18nable i18nable) {
246                 i18nables.add(i18nable);
247         }
248
249         /**
250          * Deregisters the given I18nable to be updated when the language is
251          * changed.
252          * 
253          * @param i18nable
254          *            The i18nable to register
255          */
256         public static void deregisterI18nable(I18nable i18nable) {
257                 i18nables.remove(i18nable);
258         }
259
260         //
261         // PRIVATE METHODS
262         //
263
264         /**
265          * Notifies all registered {@link I18nable}s that the language was changed.
266          */
267         private static void notifyI18nables() {
268                 for (I18nable i18nable: i18nables) {
269                         i18nable.updateI18n();
270                 }
271         }
272
273 }