Statistics
| Revision:

svn-gvsig-desktop / trunk / libraries / libInternationalization / src / org / gvsig / i18n / Messages.java @ 13649

History | View | Annotate | Download (22.6 KB)

1
/* gvSIG. Sistema de Informaci?n Geogr?fica de la Generalitat Valenciana
2
*
3
* Copyright (C) 2006-2007 IVER T.I. and Generalitat Valenciana.
4
*
5
* This program is free software; you can redistribute it and/or
6
* modify it under the terms of the GNU General Public License
7
* as published by the Free Software Foundation; either version 2
8
* of the License, or (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
* For more information, contact:
20
*
21
*  Generalitat Valenciana
22
*   Conselleria d'Infraestructures i Transport
23
*   Av. Blasco Ib??ez, 50
24
*   46010 VALENCIA
25
*   SPAIN
26
*
27
*      +34 963862235
28
*   gvsig@gva.es
29
*      www.gvsig.gva.es
30
*
31
*    or
32
*
33
*   IVER T.I. S.A
34
*   Salamanca 50
35
*   46005 Valencia
36
*   Spain
37
*
38
*   +34 963163400
39
*   dac@iver.es
40
*/
41

    
42
package org.gvsig.i18n;
43

    
44
import java.io.File;
45
import java.io.IOException;
46
import java.io.InputStream;
47
import java.net.MalformedURLException;
48
import java.net.URL;
49
import java.text.MessageFormat;
50
import java.util.ArrayList;
51
import java.util.Enumeration;
52
import java.util.IllegalFormatException;
53
import java.util.Iterator;
54
import java.util.Locale;
55
import java.util.Properties;
56
import java.util.Set;
57

    
58
import org.apache.log4j.Logger;
59

    
60
/**
61
 * <p>This class offers some methods to provide internationalization services
62
 * to other projects. All the methods are static.</p>
63
 * 
64
 * <p>The most useful method is {@link #getText(String) getText(key)} (and family),
65
 * which returns the translation associated
66
 * with the provided key. The class must be initialized before getText can be
67
 * used.</p>
68
 * 
69
 * <p>The typical usage sequence would be:</p>
70
 * <ul>
71
 * <li>Add some locale to the prefered locales list: <code>Messages.addLocale(new Locale("es"))</code></li>
72
 * <li>Add some resource file containing translations: <code>Messages.addResourceFamily("text", new File("."))</code></li>
73
 * <li>And finaly getText can be used: <code>Messages.getText("aceptar")</code></li>
74
 * </ul>
75
 * 
76
 * <p>The resource files are Java properties files, which contain <code>key=translation</code>
77
 * pairs, one
78
 * pair per line. These files must be encoded using iso-8859-1 encoding, and unicode escaped
79
 * sequences must be used to include characters outside the former encoding.
80
 * There are several ways to specify the property file to load, see the different
81
 * addResourceFamily methods for details.</p> 
82
 * 
83
 * @author Cesar Martinez Izquierdo (cesar.martinez@iver.es)
84
 *
85
 */
86
public class Messages {
87
    private static Logger logger = Logger.getLogger("Messages");
88
    private static String _CLASSNAME = "org.gvsig.i18n.Messages";
89
    
90
    /* Each entry will contain a hashmap with translations. Each hasmap
91
     * contains the translations for one language, indexed by the
92
     * translation key. The translations for language (i) in the preferred locales
93
     * list are contained in the position (i) of the localeResources list */
94
    private static ArrayList localeResources = new ArrayList(); 
95
        private static ArrayList preferredLocales = new ArrayList(); // contains the ordered list of prefered languages/locales (class Locale)
96
        
97
        /*
98
         * The language considered the origin of translations, which will
99
         * (possibly) be stored in a property file without language suffix
100
         * (ie: text.properties instead of text_es.properties).
101
         */
102
        private static String baseLanguage = "es";
103
        
104
        /**
105
         * <p>Gets the localized message associated with the provided key.
106
         * If the key is not in the dictionary, return the key and register
107
         * the failure in the log.</p>
108
         * 
109
         * <p>The <code>callerName</code> parameter is only
110
         * used as a label when logging, so any String can be used. However, a
111
         * meaningful String should be used, such as the name of the class requiring
112
         * the translation services, in order to identify the source of the failure
113
         * in the log.</p>    
114
         * 
115
         * @param key         An String which identifies the translation that we want to get. 
116
         * @param callerName  A symbolic name given to the caller of this method, to
117
         *                    show it in the log if the key was not found
118
         * @return            an String with the message associated with the provided key.
119
         *                    If the key is not in the dictionary, return the key. If the key
120
         *                    is null, return null.
121
         */
122
        public static String getText(String key, String callerName) {
123
                if (key==null) return null;
124
                for (int numLocale=0; numLocale<localeResources.size(); numLocale++) {
125
                        // try to get the translation for any of the languagues in the preferred languages list
126
                        String translation = ((Properties)localeResources.get(numLocale)).getProperty(key);
127
                        if (translation!=null && !translation.equals(""))
128
                                return translation;
129
                }
130
                logger.warn(callerName+ " -- Cannot find translation for "+key);
131
                return key;
132
        }
133
        
134
        public static String getText(String key,  String[] arguments, String callerName) {
135
                String translation = getText(key, callerName);
136
                if (translation!=null) {
137
                        try {
138
                                translation = MessageFormat.format(translation, arguments);
139
                        }
140
                        catch (IllegalFormatException ex) {
141
                                logger.error(callerName+" -- Error formating key: "+key+" -- "+translation);
142
                        }
143
                }
144
                return translation;
145
        }
146
        
147
        /**
148
         * <p>Gets the localized message associated with the provided key.
149
         * If the key is not in the dictionary or the translation is empty,
150
         * return the key and register the failure in the log.</p>
151
         * 
152
         * @param key     An String which identifies the translation that we want to get.
153
         * @return        an String with the message associated with the provided key.
154
         *                If the key is not in the dictionary or the translation is empty,
155
         *                return the key. If the key is null, return null.
156
         */
157
        public static String getText(String key) {
158
                return getText(key, _CLASSNAME);
159
        }
160
        
161
        public static String getText(String key, String[] arguments) {
162
                return getText(key, arguments, _CLASSNAME);
163
        }
164
        
165
        /**
166
         * <p>Gets the localized message associated with the provided key.
167
         * If the key is not in the dictionary or the translation is empty,
168
         * it returns null and the failure is only registered in the log if
169
         * the param log is true.</p>
170
         * 
171
         * @param key        An String which identifies the translation that we want
172
         *                                 to get.
173
         * @param log        Determines whether log a key failure or not
174
         * @return                an String with the message associated with the provided key,
175
         *                                 or null if the key is not in the dictionary or the
176
         *                                 translation is empty.
177
         */
178
        public static String getText(String key, boolean log) {
179
                return getText(key, _CLASSNAME, log);
180
        }
181
        
182
        public static String getText(String key, String[] arguments, boolean log) {
183
                String translation = getText(key, _CLASSNAME, log);
184
                if (translation!=null) {
185
                        try {
186
                                translation = MessageFormat.format(translation, arguments);
187
                        }
188
                        catch (IllegalFormatException ex) {
189
                                if (log) {
190
                                        logger.error(_CLASSNAME+" -- Error formating key: "+key+" -- "+translation);
191
                                }
192
                        }
193
                }
194
                return translation;
195
        }
196
        
197
        /**
198
         * <p>Gets the localized message associated with the provided key.
199
         * If the key is not in the dictionary, it returns null and the failure
200
         * is only registered in the log if the param log is true.</p>
201
         * 
202
         * @param key         An String which identifies the translation that we want to get.
203
         * @param callerName  A symbolic name given to the caller of this method, to
204
         *                    show it in the log if the key was not found
205
         * @param log         Determines whether log a key failure or not
206
         * @return            an String with the message associated with the provided key,
207
         *                    or null if the key is not in the dictionary.
208
         */
209
        public static String getText(String key, String callerName, boolean log) {
210
                if (key==null) return null;
211
                for (int numLocale=0; numLocale<localeResources.size(); numLocale++) {
212
                        // try to get the translation for any of the languagues in the preferred languages list
213
                        String translation = ((Properties)localeResources.get(numLocale)).getProperty(key);
214
                        if (translation!=null && !translation.equals(""))
215
                                return translation;
216
                }
217
                if (log) {
218
                        logger.warn(callerName+" -- Cannot find translation for "+key);
219
                }
220
                return null;
221
        }
222
        
223
        public static String getText(String key, String[] arguments, String callerName, boolean log) {
224
                String translation = getText(key, callerName, log);
225
                if (translation!=null) {
226
                        try {
227
                                translation = MessageFormat.format(translation, arguments);
228
                        }
229
                        catch (IllegalFormatException ex) {
230
                                if (log) {
231
                                        logger.error(callerName+" -- Error formating key: "+key+" -- "+translation);
232
                                }
233
                        }
234
                }
235
                return translation;
236
        }
237

    
238
        /**
239
         * <p>Adds an additional family of resource files containing some translations.
240
         * A family is a group of files with a common baseName.
241
         * The file must be an iso-8859-1 encoded file, which can contain any unicode
242
         * character using unicode escaped sequences, and following the syntax:
243
         * <code>key1=value1
244
         * key2=value2</code>
245
         * where 'key1' is the key used to identify the string and must not
246
         * contain the '=' symbol, and 'value1' is the associated translation.</p>
247
         * <p<For example:</p>
248
         * <code>cancel=Cancelar
249
         * accept=Aceptar</code>
250
         * <p>Only one pair key-value is allowed per line.</p>
251
         * 
252
         * <p>The actual name of the resource file to load is determined using the rules
253
         * explained in the class java.util.ResourceBundle. Summarizing, for each language
254
         * in the specified preferred locales list it will try to load a file with
255
         *  the following structure: <code>family_locale.properties</code></p>
256
         *
257
         * <p>For example, if the preferred locales list contains {"fr", "es", "en"}, and
258
         * the family name is "text", it will try to load the files "text_fr.properties",
259
         * "text_es.properties" and finally "text_en.properties".</p>
260
         * 
261
         * <p>Locales might be more specific, such us "es_AR"  (meaning Spanish from Argentina)
262
         * or "es_AR_linux" (meaning Linux system preferring Spanish from Argentina). In the
263
         * later case, it will try to load "text_es_AR_linux.properties", then
264
         * "text_es_AR.properties" if the former fails, and finally "text_es.properties".</p>
265
         * 
266
         * <p>The directory used to locate the resource file is determining by using the
267
         * getResource method from the provided ClassLoader.</p>
268
         *  
269
         * @param family    The family name (or base name) which is used to search
270
         *                  actual properties files.
271
         * @param loader    A ClassLoader which is able to find a property file matching
272
         *                                         the specified family name and the preferred locales
273
         * @see             <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
274
         */
275
        public static void addResourceFamily(String family, ClassLoader loader) {
276
                addResourceFamily(family, loader, "");
277
        }
278
        
279
        /**
280
         * <p>Adds an additional family of resource files containing some translations.
281
         * The search path to locate the files is provided by the dirList parameter.</p>
282
         * 
283
         * <p>See {@link addResourceFamily(String, ClassLoader)} for a discussion about the
284
         * format of the property files and the way to determine the candidat files
285
         * to load. Note that those methods are different in the way to locate the 
286
         * candidat files. This method searches in the provided paths (<code>dirList</code>
287
         * parameter), while the referred method searches using the getResource method
288
         * of the provided ClassLoader.</p>
289
         *  
290
         * @param family    The family name (or base name) which is used to search
291
         *                  actual properties files.
292
         * @param dirList   A list of search paths to locate the property files
293
         * @throws MalformedURLException
294
         * @see             <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
295
         */
296
        public static void addResourceFamily(String family, File[] dirList) throws MalformedURLException{
297
                // use our own classloader
298
                URL[] urls = new URL[dirList.length];                
299
        
300
                        int i;
301
                        for (i=0; i<urls.length; i++) {
302
                                urls[i] = dirList[i].toURL();
303
                        }
304
        
305
                ClassLoader loader = new MessagesClassLoader(urls);
306
                addResourceFamily(family, loader, "");
307
        }
308

    
309
        /**
310
         * <p>Adds an additional family of resource files containing some translations.
311
         * The search path to locate the files is provided by the dir parameter.</p>
312
         * 
313
         * <p>See {@link addResourceFamily(String, ClassLoader)} for a discussion about the
314
         * format of the property files and the way to determine the candidat files
315
         * to load. Note that those methods are different in the way to locate the 
316
         * candidat files. This method searches in the provided path (<code>dir</code>
317
         * parameter), while the referred method searches using the getResource method
318
         * of the provided ClassLoader.</p>
319
         *  
320
         * @param family    The family name (or base name) which is used to search
321
         *                  actual properties files.
322
         * @param dir       The search path to locate the property files
323
         * @throws MalformedURLException
324
         * @see             <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
325
         */
326
        public static void addResourceFamily(String family, File dir) throws MalformedURLException{
327
                // use our own classloader
328
                URL[] urls = new URL[1];                
329
                urls[0] = dir.toURL();
330
                ClassLoader loader = new MessagesClassLoader(urls);
331
                addResourceFamily(family, loader, "");
332
        }
333

    
334

    
335
        /**
336
         * <p>Adds an additional family of resource files containing some translations.
337
         * The search path is determined by the getResource method from the
338
         * provided ClassLoader.</p>
339
         * 
340
         * <p>This method is identical to {@link addResourceFamily(String, ClassLoader)},
341
         * except that it adds a <pode>callerName</code> parameter to show in the log.</p>
342
         * 
343
         * <p>See {@link addResourceFamily(String, ClassLoader)} for a discussion about the
344
         * format of the property files andthe way to determine the candidat files
345
         * to load.</p>
346
         *  
347
         * @param family      The family name (or base name) which is used to search
348
         *                    actual properties files.
349
         * @param loader      A ClassLoader which is able to find a property file matching
350
         *                                           the specified family name and the preferred locales
351
         * @param callerName  A symbolic name given to the caller of this method, to
352
         *                    show it in the log if there is an error
353
         * @see               <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
354
         */
355
        public static void addResourceFamily(String family, ClassLoader loader, String callerName) {
356
                String currentKey;
357
                Enumeration keys;
358
                String lang;
359
                Properties properties, translations;
360
                int totalLocales = preferredLocales.size();
361

    
362
                if (totalLocales == 0) {
363
                        // if it's empty, warn about that
364
                        logger.warn("There is not preferred languages list. Maybe the Messages class was not initialized");
365
                }
366

    
367

    
368
                for (int numLocale=0; numLocale<totalLocales; numLocale++) { // for each language
369
                        properties =  new Properties();
370
                        lang = ((Locale) preferredLocales.get(numLocale)).getLanguage();
371
                        InputStream is = loader.getResourceAsStream(family+"_"+lang+".properties");
372
                        if (is != null) {
373
                                try {
374
                                        properties.load(is);
375
                                } catch (IOException e) {
376
                                }
377
                        }
378
                        else if (lang.equals(baseLanguage)) {
379
                                // try also "text.properties" for the base language
380
                                is = loader.getResourceAsStream(family+".properties");
381
                                if (is!=null) {
382
                                        try {
383
                                                properties.load(is);
384
                                        } catch (IOException e) {
385
                                        }
386
                                }
387
                        }
388

    
389
                        translations = (Properties)localeResources.get(numLocale);
390
                        keys = properties.keys();
391
                        while (keys.hasMoreElements()) {
392
                                currentKey = (String) keys.nextElement();
393
                                if (! translations.containsKey(currentKey)) {
394
                                        translations.put(currentKey, properties.getProperty(currentKey));
395
                                }
396
                        }
397
                }
398
        }
399
        
400
        /**
401
         * <p>Adds an additional family of resource files containing some translations.</p>
402
         * 
403
         * <p>This method is identical to {@link addResourceFamily(String, ClassLoader, String)},
404
         * except that it uses the caller's class loader.</p>
405
         * 
406
         * <p>See {@link addResourceFamily(String, ClassLoader)} for a discussion about the
407
         * format of the property files and the way to determine the candidat files
408
         * to load.</p>
409
         *  
410
         * @param family      The family name (or base name) which is used to search
411
         *                    actual properties files.
412
         * @param callerName  A symbolic name given to the caller of this method, to
413
         *                    show it in the log if there is an error. This is only used
414
         *                    to show
415
         *                    something meaningful in the log, so you can use any string
416
         * @see               <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
417
         */
418
        public static void addResourceFamily(String family, String callerName) {
419
                addResourceFamily(family, Messages.class.getClassLoader(), callerName);
420
        }
421

    
422
        
423
        /**
424
         * Returns an ArrayList containing the ordered list of prefered Locales
425
         * Each element of the ArrayList is a Locale object.
426
         * 
427
         * @return an ArrayList containing the ordered list of prefered Locales
428
         * Each element of the ArrayList is a Locale object.
429
         */
430
        public static ArrayList getPreferredLocales() {
431
                return preferredLocales;
432
        }
433
        
434
        /**
435
         * <p>Sets the ordered list of preferred locales.
436
         * Each element of the ArrayList is a Locale object.</p>
437
         * 
438
         * <p>Note that calling this method does not load any translation, it just
439
         * adds the language to the preferred locales list, so this method must
440
         * be always called before the translations are loaded using
441
         * the addResourceFamily() methods.</p>
442
         * 
443
         * <p>It there was any language in the preferred locale list, the language
444
         * and its associated translations are deleted.</p>
445
         * 
446
         * 
447
         * @param preferredLocales an ArrayList containing Locale objects.
448
         * The ArrayList represents an ordered list of preferred locales
449
         */
450
        public static void setPreferredLocales(ArrayList preferredLocalesList) {
451
                // delete all existing locales
452
                Iterator oldLocales = preferredLocales.iterator();
453
                while (oldLocales.hasNext()) {
454
                        removeLocale((Locale) oldLocales.next());
455
                }
456
                
457
                // add the new locales now
458
                for (int numLocale=0; numLocale < preferredLocalesList.size(); numLocale++) {
459
                        addLocale((Locale) preferredLocalesList.get(numLocale));
460
                }
461
        }
462

    
463
        /**
464
         * Adds a Locale at the end of the ordered list of preferred locales.
465
         * Note that calling this method does not load any translation, it just
466
         * adds the language to the preferred locales list, so this method must
467
         * be always called before the translations are loaded using
468
         * the addResourceFamily() methods.
469
         * 
470
         * @param lang   A Locale object specifying the locale to add
471
         */
472
        public static void addLocale(Locale lang) {
473
                if (!preferredLocales.contains(lang)) { // avoid duplicates
474
                                preferredLocales.add(lang); // add the lang to the ordered list of preferred locales
475
                                Properties dict = new Properties();                                
476
                                localeResources.add(dict); // add a hashmap which will contain the translation for this language
477
                }
478
        }
479

    
480
        /**
481
         * Removes the specified Locale from the list of preferred locales and the
482
         * translations associated with this locale.
483
         * 
484
         * @param lang   A Locale object specifying the locale to remove
485
         * @return       True if the locale was in the preferred locales list, false otherwise
486
         */
487
        public static boolean removeLocale(Locale lang) {
488
                int numLocale = preferredLocales.indexOf(lang);
489
                if (numLocale!=-1) { // we found the locale in the list
490
                        try {
491
                                preferredLocales.remove(numLocale);
492
                                localeResources.remove(numLocale);
493
                        }
494
                        catch (IndexOutOfBoundsException ex) {
495
                                logger.warn(_CLASSNAME + "." + "removeLocale: " + ex.getLocalizedMessage());
496
                        }
497
                        return true;
498
                }
499
                return false;
500
        }
501

    
502
        /**
503
         * Cleans the translation tables (removes all the translations from memory).
504
         */
505
        public static void removeResources() {
506
                for (int numLocale=0; numLocale<localeResources.size(); numLocale++) {
507
                        ((Properties)localeResources.get(numLocale)).clear();
508
                }
509
        }
510

    
511
        /**
512
         * The number of translation keys which have been loaded till now  
513
         * (In other words: the number of available translation strings).
514
         * 
515
         * @param lang The language for which we want to know the number of translation keys
516
         * return The number of translation keys for the provided language.
517
         */
518
        protected static int size(Locale lang) {
519
                int numLocale = preferredLocales.indexOf(lang);
520
                if (numLocale!=-1) {
521
                        return ((Properties)localeResources.get(numLocale)).size();
522
                };
523
                return 0;
524
        }
525
        
526
        protected static Set keySet(Locale lang) {
527
                int numLocale = preferredLocales.indexOf(lang);
528
                if (numLocale!=-1) {
529
                        return ((Properties)localeResources.get(numLocale)).keySet();
530
                }
531
                else
532
                        return null;
533
        }
534
        
535
        /**
536
         * Checks if some locale has been added to the preferred locales
537
         * list, which is necessary before loading any translation because
538
         * only the translations for the preferred locales are loaded.
539
         * 
540
         * @return
541
         */
542
        public static boolean hasLocales() {
543
                return preferredLocales.size()>0;
544
        }
545
        
546
        /**
547
         * Gets the base language, the language considered the origin of
548
         * translations, which will be (possibly) stored in a property 
549
         * file without language suffix
550
         * (ie: text.properties instead of text_es.properties).
551
         */ 
552
        public static String getBaseLanguage() {
553
                return baseLanguage;
554
        }
555
        
556
        /**
557
         * Sets the base language, the language considered the origin of
558
         * translations, which will be (possibly)
559
         * stored in a property file without language suffix
560
         * (ie: text.properties instead of text_es.properties).
561
         * 
562
         * @param lang The base language to be set
563
         */
564
        public static void setBaseLanguage(String lang) {
565
                baseLanguage = lang;
566
        }
567
        
568
        /*
569
         * Searches the subdirectories of the provided directory, finding
570
         * all the translation files, and constructing a list of available translations.
571
         * It reports different country codes or variants, if available.
572
         * For example, if there is an en_US translation and an en_GB translation, both
573
         * locales will be present in the Vector.
574
         * 
575
         * @return
576
         */
577
        
578
        /**
579
         * 
580
         * @return A Vector containing the available locales. Each element is a Locale object
581
         */
582
        /*public static Vector getAvailableLocales() {
583
                return _availableLocales;
584
        }*/
585
        
586
        /**
587
         * 
588
         * @return A Vector containing the available languages. Each element is an String object
589
         */
590
        /*public static Vector getAvailableLanguages() {
591
                Vector availableLanguages = new Vector();
592
                Locale lang;
593
                Enumeration locales = _availableLocales.elements();
594
                while (locales.hasMoreElements()) {
595
                        lang = (Locale) locales.nextElement();
596
                        availableLanguages.add(lang.getLanguage());
597
                }
598
                return availableLanguages;
599
        }*/
600
}