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