Statistics
| Revision:

root / trunk / libraries / libInternationalization / src / org / gvsig / i18n / Messages.java @ 4736

History | View | Annotate | Download (19.9 KB)

1
package org.gvsig.i18n;
2

    
3
import java.io.*;
4
import java.net.MalformedURLException;
5
import java.net.URL;
6
import java.net.URLClassLoader;
7
import java.util.*;
8

    
9
import org.apache.log4j.Logger;
10

    
11
/**
12
 * <p>This class offers some methods to provide internationalization services
13
 * to other projects. All the methods are static.</p>
14
 * 
15
 * <p>The most useful method is {@link #getText(String) getText(key)} (and family),
16
 * which returns the translation associated
17
 * with the provided key. The class must be initialized before getText can be
18
 * used.</p>
19
 * 
20
 * <p>The typical usage sequence would be:</p>
21
 * <ul>
22
 * <li>Add some locale to the prefered locales list: <code>Messages.addLocale(new Locale("es"))</code></li>
23
 * <li>Add some resource file containing translations: <code>Messages.addResourceFamily("text", new File("."))</code></li>
24
 * <li>And finaly getText can be used: <code>Messages.getText("aceptar")</code></li>
25
 * </ul>
26
 * 
27
 * <p>The resource files are Java properties files, which contain <code>key=translation</code>
28
 * pairs, one
29
 * pair per line. These files must be encoded using iso-8859-1 encoding, and unicode escaped
30
 * sequences must be used to include characters outside the former encoding.
31
 * There are several ways to specify the property file to load, see the different
32
 * addResourceFamily methods for details.</p> 
33
 * 
34
 * @author Generalitat Valenciana and IVER Tecnolog?as de la Informaci?n S.A.
35
 *
36
 */
37
public class Messages {
38
    private static Logger logger = Logger.getLogger("Messages");
39
    private static String _CLASSNAME = "org.gvsig.i18n.Messages";
40
    private static int _INITIALSIZE = 1000;
41
    
42
        private static Hashtable localeResources = new Hashtable(_INITIALSIZE, 2);; // contains the translations, indexed by key 
43
        private static Vector _preferredLocales = new Vector(); // contains the ordered list of prefered languages/locales (class Locale)
44
//        private static Vector _availableLocales;
45
        
46
        /**
47
         * <p>Gets the localized message associated with the provided key.
48
         * If the key is not in the dictionary, return the key and register
49
         * the failure in the log.</p>
50
         * 
51
         * <p>The <code>callerName</code> parameter is only
52
         * used as a label when logging, so any String can be used. However, a
53
         * meaningful String should be used, such as the name of the class requiring
54
         * the translation services, in order to identify the source of the failure
55
         * in the log.</p>    
56
         * 
57
         * @param key         An String which identifies the translation that we want to get. 
58
         * @param callerName  A symbolic name given to the caller of this method, to
59
         *                    show it in the log if the key was not found
60
         * @return            an String with the message associated with the provided key.
61
         *                    If the key is not in the dictionary, return the key. If the key
62
         *                    is null, return null.
63
         */
64
        public static String getText(String key, String callerName) {
65
                if (key==null) return null;
66
                String translation = (String) localeResources.get(key);
67
                if (translation!=null)
68
                        return translation;
69
                logger.warn(callerName+Messages.getText("Messages._no_se_encontro_la_traduccion_para", false)+key);
70
                return key;
71
        }
72
        
73
        /**
74
         * <p>Gets the localized message associated with the provided key.
75
         * If the key is not in the dictionary, return the key and register
76
         * the failure in the log.</p>
77
         * 
78
         * @param key     An String which identifies the translation that we want to get.
79
         * @return        an String with the message associated with the provided key.
80
         *                If the key is not in the dictionary, return the key. If the key
81
         *                is null, return null.
82
         */
83
        public static String getText(String key) {
84
                return getText(key, "");
85
        }
86
        
87
        /**
88
         * <p>Gets the localized message associated with the provided key.
89
         * If the key is not in the dictionary, it returns null and the failure
90
         * is only registered in the log if the param log is true.</p>
91
         * 
92
         * @param key    An String which identifies the translation that we want to get.
93
         * @param log    Determines whether log a key failure or not
94
         * @return       an String with the message associated with the provided key,
95
         *               or null if the key is not in the dictionary.
96
         */
97
        public static String getText(String key, boolean log) {
98
                if (key==null) return null;
99
                return (String) localeResources.get(key);
100
        }
101
        
102
        /**
103
         * <p>Gets the localized message associated with the provided key.
104
         * If the key is not in the dictionary, it returns null and the failure
105
         * is only registered in the log if the param log is true.</p>
106
         * 
107
         * @param key         An String which identifies the translation that we want to get.
108
         * @param callerName  A symbolic name given to the caller of this method, to
109
         *                    show it in the log if the key was not found
110
         * @param log         Determines whether log a key failure or not
111
         * @return            an String with the message associated with the provided key,
112
         *                    or null if the key is not in the dictionary.
113
         */
114
        public static String getText(String key, String callerName, boolean log) {
115
                if (key==null) return null;
116
                return (String) localeResources.get(key);
117
        }
118

    
119
        /**
120
         * <p>Adds an additional family of resource files containing some translations.
121
         * A family is a group of files with a common baseName.
122
         * The file must be an iso-8859-1 encoded file, which can contain any unicode
123
         * character using unicode escaped sequences, and following the syntax:
124
         * <code>key1=value1
125
         * key2=value2</code>
126
         * where 'key1' is the key used to identify the string and must not
127
         * contain the '=' symbol, and 'value1' is the associated translation.</p>
128
         * <p<For example:</p>
129
         * <code>cancel=Cancelar
130
         * accept=Aceptar</code>
131
         * <p>Only one pair key-value is allowed per line.</p>
132
         * 
133
         * <p>The actual name of the resource file to load is determined using the rules
134
         * explained in the class java.util.ResourceBundle. Summarizing, for each language
135
         * in the specified preferred locales list it will try to load a file with
136
         *  the following structure: <code>family_locale.properties</code></p>
137
         *
138
         * <p>For example, if the preferred locales list contains {"fr", "es", "en"}, and
139
         * the family name is "text", it will try to load the files "text_fr.properties",
140
         * "text_es.properties" and finally "text_en.properties".</p>
141
         * 
142
         * <p>Locales might be more specific, such us "es_AR"  (meaning Spanish from Argentina)
143
         * or "es_AR_linux" (meaning Linux system preferring Spanish from Argentina). In the
144
         * later case, it will try to load "text_es_AR_linux.properties", then
145
         * "text_es_AR.properties" if the former fails, and finally "text_es.properties".</p>
146
         * 
147
         * <p>The directory used to locate the resource file is determining by using the
148
         * getResource method from the provided ClassLoader.</p>
149
         *  
150
         * @param family    The family name (or base name) which is used to search
151
         *                  actual properties files.
152
         * @param loader    A ClassLoader which is able to find a property file matching
153
         *                                         the specified family name and the preferred locales
154
         * @see             <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
155
         */
156
        public static void addResourceFamily(String family, ClassLoader loader) {
157
                String currentKey;
158
                Enumeration properties;                
159
                Enumeration languages = _preferredLocales.elements();
160
                if (languages.hasMoreElements() == false) {
161
                        // if it's empty, warn about that
162
                        logger.warn("No hay lista de idiomas preferidos. ?Quiz? olvid? inicializar la clase?");
163
                }
164
                Locale lang;                
165
                ResourceBundle bundle = null;
166
                
167
                while (languages.hasMoreElements()) { // for each language
168
                        try {
169
                                lang = (Locale) languages.nextElement();
170
                                bundle = ResourceBundle.getBundle(family, lang, loader);
171
                                if (bundle.getLocale().getLanguage().equals(lang.getLanguage()) || lang.getLanguage().equals("es")) {
172
                                        // we ensure we didn't get a fallback
173
                                        // (except if lang == "es" -- spanish is the default language, so we accept a fallback for it)
174
        
175
                                        properties = bundle.getKeys();
176
                                        while (properties.hasMoreElements()) {
177
                                                currentKey = (String) properties.nextElement();
178
                                                if (!localeResources.containsKey(currentKey)) {
179
                                                        localeResources.put(currentKey, bundle.getString(currentKey));
180
                                                }
181
                                        }
182
                                }
183
                        }
184
                        catch (MissingResourceException ex) {
185
                                logger.warn(_CLASSNAME+" MissingResourceException");
186
                                //logger.warn(_CLASSNAME+" ", ex);
187
                        }
188
                }
189
        }
190
        
191
        /**
192
         * <p>Adds an additional family of resource files containing some translations.
193
         * The search path to locate the files is provided by the dirList parameter.</p>
194
         * 
195
         * <p>See {@link addResourceFamily(String, ClassLoader)} for a discussion about the
196
         * format of the property files and the way to determine the candidat files
197
         * to load. Note that those methods are different in the way to locate the 
198
         * candidat files. This method searches in the provided paths (<code>dirList</code>
199
         * parameter), while the referred method searches using the getResource method
200
         * of the provided ClassLoader.</p>
201
         *  
202
         * @param family    The family name (or base name) which is used to search
203
         *                  actual properties files.
204
         * @param dirList   A list of search paths to locate the property files
205
         * @throws MalformedURLException
206
         * @see             <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
207
         */
208
        public static void addResourceFamily(String family, File[] dirList) throws MalformedURLException{
209
                // use our own classloader
210
                URL[] urls = new URL[dirList.length];                
211
        
212
                        int i;
213
                        for (i=0; i<urls.length; i++) {
214
                                urls[i] = dirList[i].toURL();
215
                        }
216
        
217
                ClassLoader loader = new PrivateClassLoader(urls);
218
                addResourceFamily(family, loader);
219
        }
220

    
221
        /**
222
         * <p>Adds an additional family of resource files containing some translations.
223
         * The search path to locate the files is provided by the dir parameter.</p>
224
         * 
225
         * <p>See {@link addResourceFamily(String, ClassLoader)} for a discussion about the
226
         * format of the property files and the way to determine the candidat files
227
         * to load. Note that those methods are different in the way to locate the 
228
         * candidat files. This method searches in the provided path (<code>dir</code>
229
         * parameter), while the referred method searches using the getResource method
230
         * of the provided ClassLoader.</p>
231
         *  
232
         * @param family    The family name (or base name) which is used to search
233
         *                  actual properties files.
234
         * @param dir       The search path to locate the property files
235
         * @throws MalformedURLException
236
         * @see             <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
237
         */
238
        public static void addResourceFamily(String family, File dir) throws MalformedURLException{
239
                // use our own classloader
240
                URL[] urls = new URL[1];                
241
                urls[0] = dir.toURL();
242
                ClassLoader loader = new PrivateClassLoader(urls);
243
                addResourceFamily(family, loader);
244
        }
245

    
246

    
247
        /**
248
         * <p>Adds an additional family of resource files containing some translations.
249
         * The search path is determined by the getResource method from the
250
         * provided ClassLoader.</p>
251
         * 
252
         * <p>This method is identical to {@link addResourceFamily(String, ClassLoader)},
253
         * except that it adds a <pode>callerName</code> parameter to show in the log.</p>
254
         * 
255
         * <p>See {@link addResourceFamily(String, ClassLoader)} for a discussion about the
256
         * format of the property files andthe way to determine the candidat files
257
         * to load.</p>
258
         *  
259
         * @param family      The family name (or base name) which is used to search
260
         *                    actual properties files.
261
         * @param loader      A ClassLoader which is able to find a property file matching
262
         *                                           the specified family name and the preferred locales
263
         * @param callerName  A symbolic name given to the caller of this method, to
264
         *                    show it in the log if there is an error
265
         * @see               <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
266
         */
267
        public static void addResourceFamily(String family, ClassLoader loader, String callerName) {
268
                String currentKey;
269
                Enumeration properties;                
270
                Enumeration languages = _preferredLocales.elements();
271
                if (languages.hasMoreElements() == false) {
272
                        // if it's empty, warn about that
273
                        logger.warn("No hay lista de idiomas preferidos. ?Quiz? olvid? inicializar la clase?");
274
                }                
275
                Locale lang;                
276
                ResourceBundle bundle = null;
277
                
278
                while (languages.hasMoreElements()) { // for each language
279
                        try {
280
                                lang = (Locale) languages.nextElement();                                
281
                                bundle = ResourceBundle.getBundle(family, lang, loader);
282
                                
283
                                if (bundle.getLocale().getLanguage().equals(lang.getLanguage()) || lang.getLanguage().equals("es")) {
284
                                        // we ensure we didn't got a fallback
285
                                        // (except if lang == "es" -- spanish is the default language, so we accept a fallback for it)
286
                                        
287
                                        properties = bundle.getKeys();
288
                                        while (properties.hasMoreElements()) {
289
                                                currentKey = (String) properties.nextElement();
290
                                                if (!localeResources.containsKey(currentKey)) {
291
                                                        localeResources.put(currentKey, bundle.getString(currentKey));
292
                                                }
293
                                        }
294
                                }
295
                                else {
296
                                        logger.warn(Messages.getText("Messages.las_traducciones_no_pudieron_ser_cargadas", false)+callerName+" -- "+lang.getLanguage());
297
                                }
298
                        }
299
                        catch (MissingResourceException ex) {
300
                                logger.warn(callerName+" ", ex);
301
                        }
302
                }
303
        }
304
        
305
        /**
306
         * <p>Adds an additional family of resource files containing some translations.</p>
307
         * 
308
         * <p>This method is identical to {@link addResourceFamily(String, ClassLoader, String)},
309
         * except that it uses the caller's class loader.</p>
310
         * 
311
         * <p>See {@link addResourceFamily(String, ClassLoader)} for a discussion about the
312
         * format of the property files and the way to determine the candidat files
313
         * to load.</p>
314
         *  
315
         * @param family      The family name (or base name) which is used to search
316
         *                    actual properties files.
317
         * @param callerName  A symbolic name given to the caller of this method, to
318
         *                    show it in the log if there is an error. This is only used
319
         *                    to show
320
         *                    something meaningful in the log, so you can use any string
321
         * @see               <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
322
         */
323
        public static void addResourceFamily(String family, String callerName) {
324
                String currentKey;
325
                Enumeration properties;                
326
                Enumeration languages = _preferredLocales.elements();
327
                if (languages.hasMoreElements() == false) {
328
                        // if it's empty, warn about that
329
                        logger.warn("No hay lista de idiomas preferidos. ?Quiz? olvid? inicializar la clase?");
330
                }                
331
                Locale lang;                
332
                ResourceBundle bundle = null;
333
                
334
                while (languages.hasMoreElements()) { // for each language
335
                        try {
336
                                lang = (Locale) languages.nextElement();
337
                                bundle = ResourceBundle.getBundle(family, lang);
338
                                
339
                                if (bundle.getLocale().getLanguage()!=lang.getLanguage() && lang.getLanguage()!="es") {
340
                                        // if we got a fallback, discard it (we'll try the next language first)
341
                                        // (except if lang == "es" -- spanish is the default language, so we accept a fallback for it
342
                                        bundle = null;                                
343
                                }
344
                                
345
                                properties = bundle.getKeys();
346
                                while (properties.hasMoreElements()) {
347
                                        currentKey = (String) properties.nextElement();
348
                                        if (!localeResources.containsKey(currentKey)) {
349
                                                localeResources.put(currentKey, bundle.getString(currentKey));
350
                                        }
351
                                }
352
                        }
353
                        catch (MissingResourceException ex) {
354
                                logger.warn(callerName+" ", ex);
355
                        }
356
                }
357
        }
358
        
359
        
360
        /**
361
         * Returns a vector containing the ordered list of prefered Locales
362
         * Each element of the vector is a Locale object.
363
         * 
364
         * @return a vector containing the ordered list of prefered Locales
365
         * Each element of the vector is a Locale object.
366
         */
367
        public static Vector getPreferredLocales() {
368
                return _preferredLocales;
369
        }
370
        
371
        /**
372
         * Sets the ordered list of preferred locales.
373
         * Each element of the vector is a Locale object.
374
         * 
375
         * @param preferredLocales a vector containing Locale objects.
376
         * The Vector represents an ordered list of preferred locales
377
         */
378
        public static void setPreferredLocales(Vector preferredLocales) {
379
                _preferredLocales = preferredLocales;
380
        }
381

    
382
        /**
383
         * Adds a Locale at the end of the ordered list of preferred locales.
384
         * 
385
         * @param lang   A Locale object specifying the locale to add
386
         */
387
        public static void addLocale(Locale lang) {
388
                if (_preferredLocales == null) {
389
                        _preferredLocales = new Vector();
390
                }
391
                _preferredLocales.add(lang);
392
        }
393

    
394
        /**
395
         * Removes the specified Locale from the list of preferred locales.
396
         * 
397
         * @param lang   A Locale object specifying the locale to remove
398
         */
399
        public static boolean removeLocale(Locale lang) {
400
                return _preferredLocales.remove(lang);
401
        }
402

    
403
        /**
404
         * Cleans the translation table (removes all the translations from memory).
405
         * 
406
         * @param lang   A Locale object specifying the locale to remove
407
         */
408
        public static void removeResources() {
409
                localeResources = new Hashtable(_INITIALSIZE, 2);; // contains the translations, indexed by key
410
        }
411

    
412
        /**
413
         * The number of translation keys which have been loaded till now  
414
         * (In other words: the number of available translation strings).
415
         */
416
        protected static int size() {
417
                return localeResources.size();
418
        }
419
        /**
420
         * Searches the subdirectories of the provided directory, finding
421
         * all the translation files, and constructing a list of available translations.
422
         * It reports different country codes or variants, if available.
423
         * For example, if there is an en_US translation and an en_GB translation, both
424
         * locales will be present in the Vector.
425
         * 
426
         * @return
427
         */
428
        
429
        /**
430
         * 
431
         * @return A Vector containing the available locales. Each element is a Locale object
432
         */
433
        /*public static Vector getAvailableLocales() {
434
                return _availableLocales;
435
        }*/
436
        
437
        /**
438
         * 
439
         * @return A Vector containing the available languages. Each element is an String object
440
         */
441
        /*public static Vector getAvailableLanguages() {
442
                Vector availableLanguages = new Vector();
443
                Locale lang;
444
                Enumeration locales = _availableLocales.elements();
445
                while (locales.hasMoreElements()) {
446
                        lang = (Locale) locales.nextElement();
447
                        availableLanguages.add(lang.getLanguage());
448
                }
449
                return availableLanguages;
450
        }*/
451
}
452

    
453
class PrivateClassLoader extends URLClassLoader {
454
        
455
        public PrivateClassLoader(URL[] urls) {
456
                super(urls);
457
        }
458
        
459
        public URL getResource(String res) {
460
                try {
461
                        ArrayList resource = new ArrayList();
462
                        StringTokenizer st = new StringTokenizer(res, "\\/");
463
                        
464
                        while (st.hasMoreTokens()) {
465
                                String token = st.nextToken();
466
                                resource.add(token);
467
                        }
468
                        URL ret = null;
469
                        int currentUrl;
470
                        URL[] urls = getURLs();
471
                
472
                        for (currentUrl=0; currentUrl< urls.length; currentUrl++) {
473
                                URL url = urls[currentUrl];
474
                                File file = new File(url.getFile());
475
                                if (url.getFile().endsWith("/"))
476
                                        ret = getResource(file, resource);
477
                        }
478
                        
479
                        if (ret != null) {
480
                                return ret;
481
                        }
482
                } catch (Exception e) {
483
                        e.printStackTrace();
484
                }
485
                
486
                return super.getResource(res);
487
        }
488
        
489
        /**
490
         * Busca recursivamente el recurso res en el directorio base. res es una
491
         * lista de String's con los directorios del path y base es el directorio
492
         * a partir del cual se busca dicho recurso. En cada ejecuci?n del m?todo
493
         * se toma el primer elemento de res y se busca dicho directorio en el
494
         * directorio base. Si se encuentra, ser? el directorio base para una
495
         * nueva llamada.
496
         *
497
         * @param base Directorio desde donde parte la b?squeda del recurso.
498
         * @param res Lista de strings con el path del recurso que se quiere
499
         *        encontrar
500
         *
501
         * @return URL con el recurso
502
         */
503
        private URL getResource(File base, List res) {
504
                File[] files = base.listFiles();
505
                
506
                String parte = (String) res.get(0);
507
                
508
                for (int i = 0; i < files.length; i++) {
509
                        if (files[i].getName().compareTo(parte) == 0) {
510
                                if (res.size() == 1) {
511
                                        try {
512
                                                return new URL("file:" + files[i].toString());
513
                                        } catch (MalformedURLException e) {
514
                                                return null;
515
                                        }
516
                                } else {
517
                                        return getResource(files[i], res.subList(1, res.size()));
518
                                }
519
                        }
520
                }
521
                
522
                return null;
523
        }
524
}
525