001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2007-2007 JasperSoft 008// Copyright (C) 2008-2009 Pentaho 009// All Rights Reserved. 010*/ 011package mondrian.gui; 012 013import org.apache.log4j.Logger; 014 015import java.io.File; 016import java.io.IOException; 017import java.net.JarURLConnection; 018import java.net.URL; 019import java.text.MessageFormat; 020import java.util.*; 021import java.util.jar.JarEntry; 022import java.util.jar.JarFile; 023 024public class I18n { 025 private static final Logger LOGGER = Logger.getLogger(I18n.class); 026 027 // Default to english 028 private Locale currentLocale = Locale.ENGLISH; 029 030 private ResourceBundle guiBundle = null; 031 private ResourceBundle languageBundle = null; 032 033 private static String defaultIcon = "nopic"; 034 035 private static List<LanguageChangedListener> languageChangedListeners = 036 new ArrayList<LanguageChangedListener>(); 037 038 public static void addOnLanguageChangedListener( 039 LanguageChangedListener listener) 040 { 041 languageChangedListeners.add(listener); 042 } 043 044 public I18n(ResourceBundle guiBundle, ResourceBundle languageBundle) { 045 this.guiBundle = guiBundle; 046 this.languageBundle = languageBundle; 047 } 048 049 public static List<Locale> getListOfAvailableLanguages(Class cl) { 050 List<Locale> supportedLocales = new ArrayList<Locale>(); 051 052 try { 053 Set<String> names = getResourcesInPackage(cl, cl.getName()); 054 for (String name : names) { 055 // From 056 // '../../<application>_en.properties' 057 // or 058 // '../../<application>_en_UK.properties' 059 // To 060 // 'en' OR 'en_UK_' OR even en_UK_Brighton dialect 061 062 String lang = name.substring(name.lastIndexOf('/') + 1); 063 064 // only accept resources with extension '.properties' 065 if (lang.indexOf(".properties") < 0) { 066 continue; 067 } 068 069 lang = lang.substring(0, lang.indexOf(".properties")); 070 071 StringTokenizer tokenizer = new StringTokenizer(lang, "_"); 072 if (tokenizer.countTokens() <= 1) { 073 continue; 074 } 075 076 String language = ""; 077 String country = ""; 078 String variant = ""; 079 080 int i = 0; 081 while (tokenizer.hasMoreTokens()) { 082 String token = tokenizer.nextToken(); 083 084 switch (i) { 085 case 0: 086 //the word <application> 087 break; 088 case 1: 089 language = token; 090 break; 091 case 2: 092 country = token; 093 break; 094 case 3: 095 variant = token; 096 break; 097 default: 098 // 099 } 100 i++; 101 } 102 103 Locale model = new Locale(language, country, variant); 104 supportedLocales.add(model); 105 } 106 } catch (Exception e) { 107 LOGGER.error("getListOfAvailableLanguages", e); 108 } 109 110 // Sort the list. Probably should use the current locale when getting 111 // the DisplayLanguage so the sort order is correct for the user. 112 113 Collections.sort( 114 supportedLocales, 115 new Comparator<Object>() { 116 public int compare(Object lhs, Object rhs) { 117 String ls = ((Locale) lhs).getDisplayLanguage(); 118 String rs = ((Locale) rhs).getDisplayLanguage(); 119 120 // TODO: this is not very nice - We should introduce a 121 // MyLocale 122 if (ls.equals("pap")) { 123 ls = "Papiamentu"; 124 } 125 if (rs.equals("pap")) { 126 rs = "Papiamentu"; 127 } 128 129 return ls.compareTo(rs); 130 } 131 }); 132 133 return supportedLocales; 134 } 135 136 /** 137 * Enumerates the resouces in a give package name. 138 * This works even if the resources are loaded from a jar file! 139 * 140 * <p/>Adapted from code by mikewse 141 * on the java.sun.com message boards. 142 * http://forum.java.sun.com/thread.jsp?forum=22&thread=30984 143 * 144 * <p>The resulting set is deterministically ordered. 145 * 146 * @param coreClass Class for class loader to find the resources 147 * @param packageName The package to enumerate 148 * @return A Set of Strings for each resouce in the package. 149 */ 150 public static Set<String> getResourcesInPackage( 151 Class coreClass, 152 String packageName) 153 throws IOException 154 { 155 String localPackageName; 156 if (packageName.endsWith("/")) { 157 localPackageName = packageName; 158 } else { 159 localPackageName = packageName + '/'; 160 } 161 162 ClassLoader cl = coreClass.getClassLoader(); 163 Enumeration<URL> dirEnum = cl.getResources(localPackageName); 164 Set<String> names = new LinkedHashSet<String>(); // deterministic 165 while (dirEnum.hasMoreElements()) { 166 URL resUrl = dirEnum.nextElement(); 167 168 // Pointing to filesystem directory 169 if (resUrl.getProtocol().equals("file")) { 170 try { 171 File dir = new File(resUrl.getFile()); 172 File[] files = dir.listFiles(); 173 if (files != null) { 174 for (int i = 0; i < files.length; i++) { 175 File file = files[i]; 176 if (file.isDirectory()) { 177 continue; 178 } 179 names.add(localPackageName + file.getName()); 180 } 181 } 182 } catch (Exception ex) { 183 ex.printStackTrace(); 184 } 185 186 // Pointing to Jar file 187 } else if (resUrl.getProtocol().equals("jar")) { 188 JarURLConnection jconn = 189 (JarURLConnection) resUrl.openConnection(); 190 JarFile jfile = jconn.getJarFile(); 191 Enumeration entryEnum = jfile.entries(); 192 while (entryEnum.hasMoreElements()) { 193 JarEntry entry = (JarEntry) entryEnum.nextElement(); 194 String entryName = entry.getName(); 195 // Exclude our own directory 196 if (entryName.equals(localPackageName)) { 197 continue; 198 } 199 String parentDirName = 200 entryName.substring(0, entryName.lastIndexOf('/') + 1); 201 if (!parentDirName.equals(localPackageName)) { 202 continue; 203 } 204 names.add(entryName); 205 } 206 } else { 207 // Invalid classpath entry 208 } 209 } 210 211 return names; 212 } 213 214 215 public void setCurrentLocale(String language) { 216 setCurrentLocale(language, null); 217 } 218 219 public void setCurrentLocale(String language, String country) { 220 if (language != null && !language.equals("")) { 221 if (country != null && !country.equals("")) { 222 setCurrentLocale(new Locale(language, country)); 223 } else { 224 setCurrentLocale(new Locale(language)); 225 } 226 } else { 227 setCurrentLocale(Locale.getDefault()); 228 } 229 } 230 231 public void setCurrentLocale(Locale locale) { 232 currentLocale = locale; 233 languageBundle = null; 234 235 for (LanguageChangedListener listener : languageChangedListeners) { 236 try { 237 listener.languageChanged(new LanguageChangedEvent(locale)); 238 } catch (Exception ex) { 239 LOGGER.error("setCurrentLocale", ex); 240 } 241 } 242 } 243 244 public Locale getCurrentLocale() { 245 if (currentLocale == null) { 246 currentLocale = Locale.getDefault(); 247 } 248 return currentLocale; 249 } 250 251 public String getGUIReference(String reference) { 252 try { 253 if (guiBundle == null) { 254 throw new Exception("No GUI bundle"); 255 } 256 return guiBundle.getString(reference); 257 } catch (MissingResourceException ex) { 258 LOGGER.error( 259 "Can't find the translation for key = " + reference, ex); 260 throw ex; 261 } catch (Exception ex) { 262 LOGGER.error("Exception loading reference = " + reference, ex); 263 return guiBundle.getString(defaultIcon); 264 } 265 } 266 267 /** 268 * Retreives a resource string using the current locale. 269 * 270 * @param stringId The resource string identifier 271 * @return The locale specific string 272 */ 273 public String getString(String stringId) { 274 return getString(stringId, getCurrentLocale()); 275 } 276 277 /** 278 * Retreives a resource string using the current locale, with a default. 279 * 280 * @param stringId The resource string identifier 281 * @param defaultValue If no resource for the stringID is specified, use 282 * this default value 283 * @return The locale specific string 284 */ 285 public String getString(String stringId, String defaultValue) { 286 return getString(stringId, getCurrentLocale(), defaultValue); 287 } 288 289 /** 290 * Retrieves a resource string using the current locale. 291 * 292 * @param stringId The resource string identifier 293 * @param defaultValue The default value for the resource string 294 * @param args arguments to be inserted into the resource string 295 * @return The locale specific string 296 */ 297 public String getFormattedString( 298 String stringId, 299 String defaultValue, 300 Object... args) 301 { 302 String pattern = getString(stringId, getCurrentLocale(), defaultValue); 303 MessageFormat mf = 304 new java.text.MessageFormat(pattern, getCurrentLocale()); 305 return mf.format(args); 306 } 307 308 /** 309 * Retreive a resource string using the given locale. The stringID is the 310 * default. 311 * 312 * @param stringId The resource string identifier 313 * @param currentLocale required Locale for resource 314 * @return The locale specific string 315 */ 316 private String getString(String stringId, Locale currentLocale) { 317 return getString(stringId, currentLocale, stringId); 318 } 319 320 /** 321 * Retreive a resource string using the given locale. Use the default if 322 * there is nothing for the given Locale. 323 * 324 * @param stringId The resource string identifier 325 * @param currentLocale required Locale for resource 326 * @param defaultValue The default value for the resource string 327 * @return The locale specific string 328 */ 329 public String getString( 330 String stringId, 331 Locale currentLocale, 332 String defaultValue) 333 { 334 try { 335 if (languageBundle == null) { 336 throw new Exception("No language bundle"); 337 } 338 return languageBundle.getString(stringId); 339 } catch (MissingResourceException ex) { 340 LOGGER.error( 341 "Can't find the translation for key = " 342 + stringId 343 + ": using default (" 344 + defaultValue 345 + ")", ex); 346 } catch (Exception ex) { 347 LOGGER.error("Exception loading stringID = " + stringId, ex); 348 } 349 return defaultValue; 350 } 351 352 public static String getCurrentLocaleID() { 353 return ""; 354 } 355} 356 357// End I18n.java