Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.compat.cdc / org.gvsig.i18n / src / main / java / org / gvsig / i18n / Messages.java @ 42761

History | View | Annotate | Download (29 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 3
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 * For any additional information, do not hesitate to contact us
22
 * at info AT gvsig.com, or visit our website www.gvsig.com.
23
 */
24

    
25
package org.gvsig.i18n;
26

    
27
import java.io.File;
28
import java.io.IOException;
29
import java.io.InputStream;
30
import java.net.MalformedURLException;
31
import java.net.URL;
32
import java.text.MessageFormat;
33
import java.util.ArrayList;
34
import java.util.Enumeration;
35
import java.util.HashSet;
36
import java.util.IllegalFormatException;
37
import java.util.Iterator;
38
import java.util.List;
39
import java.util.Locale;
40
import java.util.Properties;
41
import java.util.Set;
42

    
43
import org.slf4j.Logger;
44
import org.slf4j.LoggerFactory;
45

    
46
/**
47
 * <p>This class offers some methods to provide internationalization services
48
 * to other projects. All the methods are static.</p>
49
 *
50
 * <p>The most useful method is {@link #getText(String) getText(key)} (and family),
51
 * which returns the translation associated
52
 * with the provided key. The class must be initialized before getText can be
53
 * used.</p>
54
 *
55
 * <p>The typical usage sequence would be:</p>
56
 * <ul>
57
 * <li>Add some locale to the prefered locales list: <code>Messages.addLocale(new Locale("es"))</code></li>
58
 * <li>Add some resource file containing translations: <code>Messages.addResourceFamily("text", new File("."))</code></li>
59
 * <li>And finaly getText can be used: <code>Messages.getText("aceptar")</code></li>
60
 * </ul>
61
 *
62
 * <p>The resource files are Java properties files, which contain <code>key=translation</code>
63
 * pairs, one
64
 * pair per line. These files must be encoded using iso-8859-1 encoding, and unicode escaped
65
 * sequences must be used to include characters outside the former encoding.
66
 * There are several ways to specify the property file to load, see the different
67
 * addResourceFamily methods for details.</p>
68
 *
69
 * @author Cesar Martinez Izquierdo (cesar.martinez@iver.es)
70
 *
71
 */
72
public class Messages {
73
    
74
    private static class FamilyDescriptor {
75
        String family = null;
76
        ClassLoader loader = null;
77
        String callerName = null;
78
        
79
        public FamilyDescriptor(String family, ClassLoader loader, String callerName ) {
80
            this.family  = family;
81
            this.loader = loader;
82
            this.callerName = callerName;
83
        }
84
    }
85
    
86
    private static Logger logger = LoggerFactory.getLogger("Messages");
87
    private static String _CLASSNAME = "org.gvsig.i18n.Messages";
88
    private static Locale currentLocale;
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
    private static Set notTranslatedKeys = new HashSet();
97

    
98

    
99
        /* Set of resource families and classloaders used to load i18n resources. */
100
        private static Set resourceFamilies = new HashSet();
101
        private static Set classLoaders = new HashSet();
102

    
103
        private static List<FamilyDescriptor> familyDescriptors = new ArrayList<>();
104
        
105
        /*
106
         * The language considered the origin of translations, which will
107
         * (possibly) be stored in a property file without language suffix
108
         * (ie: text.properties instead of text_es.properties).
109
         */
110
        private static String baseLanguage = "es";
111
        private static Locale baseLocale = new Locale(baseLanguage);
112

    
113
        /**
114
         * <p>Gets the localized message associated with the provided key.
115
         * If the key is not in the dictionary, return the key and register
116
         * the failure in the log.</p>
117
         *
118
         * <p>The <code>callerName</code> parameter is only
119
         * used as a label when logging, so any String can be used. However, a
120
         * meaningful String should be used, such as the name of the class requiring
121
         * the translation services, in order to identify the source of the failure
122
         * in the log.</p>
123
         *
124
         * @param key         An String which identifies the translation that we want to get.
125
         * @param callerName  A symbolic name given to the caller of this method, to
126
         *                    show it in the log if the key was not found
127
         * @return            an String with the message associated with the provided key.
128
         *                    If the key is not in the dictionary, return the key. If the key
129
         *                    is null, return null.
130
         */
131
        public static String getText(String key, String callerName) {
132
                if (key==null) {
133
                        return null;
134
                }
135
                for (int numLocale=0; numLocale<localeResources.size(); numLocale++) {
136
                        // try to get the translation for any of the languagues in the preferred languages list
137
                        String translation = ((Properties)localeResources.get(numLocale)).getProperty(key);
138
                        if (translation!=null && !translation.equals("")) {
139
                                return translation;
140
                        }
141
                }
142
                addNotTranslatedKey(key, callerName, true);
143
                return key;
144
        }
145

    
146
        public static String getText(String key,  String[] arguments, String callerName) {
147
                String translation = getText(key, callerName);
148
                if (translation!=null && arguments!=null ) {
149
                        try {
150
                                translation = MessageFormat.format(translation, arguments);
151
                        }
152
                        catch (IllegalFormatException ex) {
153
                                logger.error(callerName+" -- Error formating key: "+key+" -- "+translation);
154
                        }
155
                }
156
                return translation;
157
        }
158
        
159
        public static String translate(String message, String[] args) {
160
                String msg = message;
161
                if (msg == null) {
162
                        return "";
163
                }
164
                msg = getText(msg, args);
165
                if (msg == null) {
166
                        msg = "_" + message.replace("_", " ");
167
                }
168
                return msg;
169
        }
170

    
171
        public static String translate(String message) {
172
                String msg = message;
173
                if (msg == null) {
174
                        return "";
175
                }
176
                msg = getText(msg, (String[]) null);
177
                if (msg == null || msg.startsWith("_")) {
178
                        msg = "_" + message.replace("_", " ");
179
                }
180
                return msg;
181
        }
182

    
183
        /**
184
         * <p>Gets the localized message associated with the provided key.
185
         * If the key is not in the dictionary or the translation is empty,
186
         * return the key and register the failure in the log.</p>
187
         *
188
         * @param key     An String which identifies the translation that we want to get.
189
         * @return        an String with the message associated with the provided key.
190
         *                If the key is not in the dictionary or the translation is empty,
191
         *                return the key. If the key is null, return null.
192
         */
193
        public static String getText(String key) {
194
                return getText(key, _CLASSNAME);
195
        }
196

    
197
        public static String getText(String key, String[] arguments) {
198
                return getText(key, arguments, _CLASSNAME);
199
        }
200

    
201
        
202
        /**
203
         * <p>Gets the localized message associated with the provided key.
204
         * If the key is not in the dictionary or the translation is empty,
205
         * it returns null and the failure is only registered in the log if
206
         * the param log is true.</p>
207
         *
208
         * @param key        An String which identifies the translation that we want
209
         *                                 to get.
210
         * @param log        Determines whether log a key failure or not
211
         * @return                an String with the message associated with the provided key,
212
         *                                 or null if the key is not in the dictionary or the
213
         *                                 translation is empty.
214
         */
215
        public static String getText(String key, boolean log) {
216
                return getText(key, _CLASSNAME, log);
217
        }
218

    
219
        public static String getText(String key, String[] arguments, boolean log) {
220
                String translation = getText(key, _CLASSNAME, log);
221
                if (translation!=null && arguments!=null ) {
222
                        try {
223
                                translation = MessageFormat.format(translation, arguments);
224
                        } catch (IllegalFormatException ex) {
225
                                if (log) {
226
                                        logger.error(_CLASSNAME+" -- Error formating key: "+key+" -- "+translation);
227
                                }
228
                        }
229
                }
230
                return translation;
231
        }
232

    
233
        /**
234
         * <p>Gets the localized message associated with the provided key.
235
         * If the key is not in the dictionary, it returns null and the failure
236
         * is only registered in the log if the param log is true.</p>
237
         *
238
         * @param key         An String which identifies the translation that we want to get.
239
         * @param callerName  A symbolic name given to the caller of this method, to
240
         *                    show it in the log if the key was not found
241
         * @param log         Determines whether log a key failure or not
242
         * @return            an String with the message associated with the provided key,
243
         *                    or null if the key is not in the dictionary.
244
         */
245
        public static String getText(String key, String callerName, boolean log) {
246
                if (key==null) {
247
                        return null;
248
                }
249
                for (int numLocale=0; numLocale<localeResources.size(); numLocale++) {
250
                        // try to get the translation for any of the languagues in the preferred languages list
251
                        String translation = ((Properties)localeResources.get(numLocale)).getProperty(key);
252
                        if (translation!=null && !translation.equals("")) {
253
                                return translation;
254
                        }
255
                }
256
                addNotTranslatedKey(key, callerName, log);
257
                return null;
258
        }
259

    
260
        public static String getText(String key, String[] arguments, String callerName, boolean log) {
261
                String translation = getText(key, callerName, log);
262
                if (translation!=null) {
263
                        try {
264
                                translation = MessageFormat.format(translation, arguments);
265
                        }
266
                        catch (IllegalFormatException ex) {
267
                                if (log) {
268
                                        logger.error(callerName+" -- Error formating key: "+key+" -- "+translation);
269
                                }
270
                        }
271
                }
272
                return translation;
273
        }
274

    
275
        /**
276
         * <p>Adds an additional family of resource files containing some translations.
277
         * A family is a group of files with a common baseName.
278
         * The file must be an iso-8859-1 encoded file, which can contain any unicode
279
         * character using unicode escaped sequences, and following the syntax:
280
         * <code>key1=value1
281
         * key2=value2</code>
282
         * where 'key1' is the key used to identify the string and must not
283
         * contain the '=' symbol, and 'value1' is the associated translation.</p>
284
         * <p>For example:</p>
285
         * <code>cancel=Cancelar
286
         * accept=Aceptar</code>
287
         * <p>Only one pair key-value is allowed per line.</p>
288
         *
289
         * <p>The actual name of the resource file to load is determined using the rules
290
         * explained in the class java.util.ResourceBundle. Summarizing, for each language
291
         * in the specified preferred locales list it will try to load a file with
292
         *  the following structure: <code>family_locale.properties</code></p>
293
         *
294
         * <p>For example, if the preferred locales list contains {"fr", "es", "en"}, and
295
         * the family name is "text", it will try to load the files "text_fr.properties",
296
         * "text_es.properties" and finally "text_en.properties".</p>
297
         *
298
         * <p>Locales might be more specific, such us "es_AR"  (meaning Spanish from Argentina)
299
         * or "es_AR_linux" (meaning Linux system preferring Spanish from Argentina). In the
300
         * later case, it will try to load "text_es_AR_linux.properties", then
301
         * "text_es_AR.properties" if the former fails, and finally "text_es.properties".</p>
302
         *
303
         * <p>The directory used to locate the resource file is determining by using the
304
         * getResource method from the provided ClassLoader.</p>
305
         *
306
         * @param family    The family name (or base name) which is used to search
307
         *                  actual properties files.
308
         * @param loader    A ClassLoader which is able to find a property file matching
309
         *                                         the specified family name and the preferred locales
310
         * @see             <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
311
         */
312
        public static void addResourceFamily(String family, ClassLoader loader) {
313
                addResourceFamily(family, loader, "");
314
        }
315

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

    
337
                        int i;
338
                        for (i=0; i<urls.length; i++) {
339
                                urls[i] = dirList[i].toURL();
340
                        }
341

    
342
                ClassLoader loader = new MessagesClassLoader(urls);
343
                addResourceFamily(family, loader, "");
344
        }
345

    
346
        /**
347
         * <p>Adds an additional family of resource files containing some translations.
348
         * The search path to locate the files is provided by the dir parameter.</p>
349
         *
350
         * <p>See {@link #addResourceFamily(String, ClassLoader)} for a discussion about the
351
         * format of the property files and the way to determine the candidat files
352
         * to load. Note that those methods are different in the way to locate the
353
         * candidat files. This method searches in the provided path (<code>dir</code>
354
         * parameter), while the referred method searches using the getResource method
355
         * of the provided ClassLoader.</p>
356
         *
357
         * @param family    The family name (or base name) which is used to search
358
         *                  actual properties files.
359
         * @param folder       The search path to locate the property files
360
         * @throws MalformedURLException
361
         * @see             <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
362
         */
363
        public static void addResourceFamily(String family, File folder) throws MalformedURLException{
364
                // use our own classloader
365
                URL[] urls = new URL[] { folder.toURI().toURL() };
366
                ClassLoader loader = new MessagesClassLoader(urls);
367
                for (FamilyDescriptor familyDescriptor : familyDescriptors) {
368
                    if( familyDescriptor.callerName.equals(folder.getAbsolutePath()) &&
369
                        familyDescriptor.family.equals(family) ) {
370
                        // Already added
371
                        return;
372
                    }
373
                }
374
                addResourceFamily(family, loader, folder.getAbsolutePath());
375
        }
376

    
377

    
378
        /**
379
         * <p>Adds an additional family of resource files containing some translations.
380
         * The search path is determined by the getResource method from the
381
         * provided ClassLoader.</p>
382
         *
383
         * <p>This method is identical to {@link #addResourceFamily(String, ClassLoader)},
384
         * except that it adds a <code>callerName</code> parameter to show in the log.</p>
385
         *
386
         * <p>See {@link #addResourceFamily(String, ClassLoader)} for a discussion about the
387
         * format of the property files andthe way to determine the candidat files
388
         * to load.</p>
389
         *
390
         * @param family      The family name (or base name) which is used to search
391
         *                    actual properties files.
392
         * @param loader      A ClassLoader which is able to find a property file matching
393
         *                                           the specified family name and the preferred locales
394
         * @param callerName  A symbolic name given to the caller of this method, to
395
         *                    show it in the log if there is an error
396
         * @see               <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
397
         */
398
        public static void addResourceFamily(String family, ClassLoader loader, String callerName) {
399
//                String currentKey;
400
//                Enumeration keys;
401
                Locale lang;
402
//                Properties properties;
403
                Properties translations;
404
                int totalLocales = preferredLocales.size();
405

    
406
                if (totalLocales == 0) {
407
                        // if it's empty, warn about that
408
                        logger.warn("There is not preferred languages list. Maybe the Messages class was not initialized");
409
                }
410

    
411
                familyDescriptors.add( new FamilyDescriptor(family,loader,callerName));
412
                
413
                resourceFamilies.add(family);
414
                classLoaders.add(loader);
415

    
416
                for (int numLocale=0; numLocale<totalLocales; numLocale++) { // for each language
417
//                        properties =  new Properties();
418

    
419
                        lang = (Locale) preferredLocales.get(numLocale);
420
                        translations = (Properties) localeResources.get(numLocale);
421

    
422
                        addResourceFamily(lang, translations, family, loader, callerName);
423
                }
424
        }
425
        
426
        private static void addResourceFamily(Locale lang, Properties translations,
427
                        String family, ClassLoader loader, String callerName) {
428
                logger.debug("addResourceFamily "+lang.toString()+", "+family+", "+loader.toString());
429
                
430
                Properties properties = new Properties();
431
                String langCode = lang.toString();
432
                String resource = family.replace('.', '/') + "_" + langCode + ".properties";
433
                URL resourceURL = loader.getResource(resource);
434
                InputStream is = loader.getResourceAsStream(resource);
435
                if( is==null && langCode.contains("_") ) {
436
                        try {
437
                                langCode = langCode.split("_")[0];
438
                                resource = family.replace('.', '/') + "_" + langCode + ".properties";
439
                                resourceURL = loader.getResource(resource);
440
                                is = loader.getResourceAsStream(resource);
441
                                if( is==null ) {
442
                                        resource = family.replace('.', '/') +  ".properties";
443
                                        resourceURL = loader.getResource(resource);
444
                                        is = loader.getResourceAsStream(resource);
445
                                }
446
                        } catch(Exception ex) {
447
                                // Do nothing, is are null and are handled later
448
                        }
449
                }
450
                if (is != null) {
451
                        try {
452
                                properties.load(is);
453
                        } catch (IOException e) {
454
                        }
455
                } else if (lang.equals(baseLocale)) {
456
                        // try also "text.properties" for the base language
457
                        is = loader.getResourceAsStream(family.replace('.', '/')
458
                                        + ".properties");
459

    
460

    
461
                        if (is != null) {
462
                                try {
463
                                        properties.load(is);
464
                                } catch (IOException e) {
465
                                }
466
                        }
467

    
468
                }
469
                if( resourceURL!=null && logger.isDebugEnabled() ) {
470
                    logger.debug("Load resources from '"+resourceURL.toString()+"' with classloader {"+loader.toString()+"}.");
471
                }
472
                Enumeration keys = properties.keys();
473
                while (keys.hasMoreElements()) {
474
                        String currentKey = (String) keys.nextElement();
475
                        if (!translations.containsKey(currentKey)) {
476
                                translations.put(currentKey, properties.getProperty(currentKey));
477
                        }
478
                }
479

    
480
        }
481

    
482
        /**
483
         * <p>Adds an additional family of resource files containing some translations.</p>
484
         *
485
         * <p>This method is identical to {@link #addResourceFamily(String, ClassLoader, String)},
486
         * except that it uses the caller's class loader.</p>
487
         *
488
         * <p>See {@link #addResourceFamily(String, ClassLoader)} for a discussion about the
489
         * format of the property files and the way to determine the candidat files
490
         * to load.</p>
491
         *
492
         * @param family      The family name (or base name) which is used to search
493
         *                    actual properties files.
494
         * @param callerName  A symbolic name given to the caller of this method, to
495
         *                    show it in the log if there is an error. This is only used
496
         *                    to show
497
         *                    something meaningful in the log, so you can use any string
498
         * @see               <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
499
         */
500
        public static void addResourceFamily(String family, String callerName) {
501
                addResourceFamily(family, Messages.class.getClassLoader(), callerName);
502
        }
503

    
504

    
505
        /**
506
         * Returns an ArrayList containing the ordered list of prefered Locales
507
         * Each element of the ArrayList is a Locale object.
508
         *
509
         * @return an ArrayList containing the ordered list of prefered Locales
510
         * Each element of the ArrayList is a Locale object.
511
         */
512
        public static ArrayList getPreferredLocales() {
513
                return preferredLocales;
514
        }
515

    
516
        /**
517
         * <p>Sets the ordered list of preferred locales.
518
         * Each element of the ArrayList is a Locale object.</p>
519
         *
520
         * <p>Note that calling this method does not load any translation, it just
521
         * adds the language to the preferred locales list, so this method must
522
         * be always called before the translations are loaded using
523
         * the addResourceFamily() methods.</p>
524
         *
525
         * <p>It there was any language in the preferred locale list, the language
526
         * and its associated translations are deleted.</p>
527
         *
528
         *
529
         * @param preferredLocalesList an ArrayList containing Locale objects.
530
         * The ArrayList represents an ordered list of preferred locales
531
         */
532
        public static void setPreferredLocales(ArrayList preferredLocalesList) {
533
                logger.info("setPreferredLocales "+preferredLocalesList.toString());
534
                // delete all existing locales
535
                Iterator oldLocales = preferredLocales.iterator();
536
                while (oldLocales.hasNext()) {
537
                        removeLocale((Locale) oldLocales.next());
538
                }
539

    
540
                // add the new locales now
541
                for (int numLocale=0; numLocale < preferredLocalesList.size(); numLocale++) {
542
                        addLocale((Locale) preferredLocalesList.get(numLocale));
543
                }
544
        }
545

    
546
        public static Locale getCurrentLocale() {
547
            return currentLocale;
548
        }
549

    
550
        /**
551
         * 
552
         * @param locale
553
         * @deprecated  use setCurrentLocale(Locale locale, Locale alternatives[]) or LocaleManager.setCurrentLocale
554
         */
555
        public static void setCurrentLocale(Locale locale) {
556
            Locale alternatives[] = null;
557

    
558
            String localeStr = locale.getLanguage();
559
            if ( localeStr.equals("es") || 
560
                 localeStr.equals("ca") ||
561
                 localeStr.equals("gl") || 
562
                 localeStr.equals("eu") ||
563
                 localeStr.equals("vl") ) {
564
                alternatives = new Locale[2];
565
                alternatives[0] = new Locale("es");
566
                alternatives[1] = new Locale("en");
567
            } else {
568
                // prefer English for the rest
569
                alternatives = new Locale[2];
570
                alternatives[0] = new Locale("en");
571
                alternatives[1] = new Locale("es");
572
            }
573
            setCurrentLocale(locale, alternatives);
574
        }
575
        
576
        public static void setCurrentLocale(Locale locale, Locale alternatives[]) {
577
            logger.info("setCurrentLocale "+locale.toString());
578
            
579
            resourceFamilies = new HashSet();
580
            classLoaders = new HashSet();
581
            localeResources = new ArrayList();
582
            preferredLocales = new ArrayList();
583
            notTranslatedKeys = new HashSet();            
584
            
585
            addLocale(locale);
586
            for( int i=0 ; i<alternatives.length; i++ ) {
587
                addLocale(alternatives[i]);
588
            }
589
            for( int curlocale=0; curlocale<preferredLocales.size(); curlocale++) {
590
                for( int curfamily=0; curfamily<familyDescriptors.size(); curfamily++) {
591
                     FamilyDescriptor family = (FamilyDescriptor) familyDescriptors.get(curfamily);
592
                     addResourceFamily(
593
                             (Locale) preferredLocales.get(curlocale),
594
                             (Properties) localeResources.get(curlocale),
595
                             family.family,
596
                             family.loader,
597
                             family.callerName);
598
                }
599
            }
600
            currentLocale = locale;
601
            Locale.setDefault(locale);
602
        }
603

    
604
        /**
605
         * Adds a Locale at the end of the ordered list of preferred locales.
606
         * Note that calling this method does not load any translation, it just
607
         * adds the language to the preferred locales list, so this method must
608
         * be always called before the translations are loaded using
609
         * the addResourceFamily() methods.
610
         *
611
         * @param lang   A Locale object specifying the locale to add
612
         */
613
        public static void addLocale(Locale lang) {
614
                if (!preferredLocales.contains(lang)) { // avoid duplicates
615
                    logger.info("addLocale "+lang.toString());
616
                    preferredLocales.add(lang); // add the lang to the ordered list of preferred locales
617
                    Properties dict = new Properties();
618
                    localeResources.add(dict); // add a hashmap which will contain the translation for this language
619
                }
620
        }
621

    
622
        /**
623
         * Removes the specified Locale from the list of preferred locales and the
624
         * translations associated with this locale.
625
         *
626
         * @param lang   A Locale object specifying the locale to remove
627
         * @return       True if the locale was in the preferred locales list, false otherwise
628
         */
629
        public static boolean removeLocale(Locale lang) {
630
                int numLocale = preferredLocales.indexOf(lang);
631
                if (numLocale!=-1) { // we found the locale in the list
632
                        try {
633
                                preferredLocales.remove(numLocale);
634
                                localeResources.remove(numLocale);
635
                        }
636
                        catch (IndexOutOfBoundsException ex) {
637
                                logger.warn(_CLASSNAME + "." + "removeLocale: " + ex.getLocalizedMessage(), ex);
638
                        }
639
                        return true;
640
                }
641
                return false;
642
        }
643

    
644
        /**
645
         * Cleans the translation tables (removes all the translations from memory).
646
         */
647
        public static void removeResources() {
648
                for (int numLocale=0; numLocale<localeResources.size(); numLocale++) {
649
                        ((Properties)localeResources.get(numLocale)).clear();
650
                }
651
        }
652

    
653
        /**
654
         * The number of translation keys which have been loaded till now
655
         * (In other words: the number of available translation strings).
656
         *
657
         * @param lang The language for which we want to know the number of translation keys
658
         * @return The number of translation keys for the provided language.
659
         */
660
        protected static int size(Locale lang) {
661
                int numLocale = preferredLocales.indexOf(lang);
662
                if (numLocale!=-1) {
663
                        return ((Properties)localeResources.get(numLocale)).size();
664
                };
665
                return 0;
666
        }
667

    
668
        protected static Set keySet(Locale lang) {
669
                int numLocale = preferredLocales.indexOf(lang);
670
                if (numLocale!=-1) {
671
                        return ((Properties)localeResources.get(numLocale)).keySet();
672
                } else {
673
                        return null;
674
                }
675
        }
676

    
677
        /**
678
         * Checks if some locale has been added to the preferred locales
679
         * list, which is necessary before loading any translation because
680
         * only the translations for the preferred locales are loaded.
681
         *
682
         * @return
683
         */
684
        public static boolean hasLocales() {
685
                return preferredLocales.size()>0;
686
        }
687

    
688
        /**
689
         * Gets the base language, the language considered the origin of
690
         * translations, which will be (possibly) stored in a property
691
         * file without language suffix
692
         * (ie: text.properties instead of text_es.properties).
693
         * @return the base language name
694
         */
695
        public static String getBaseLanguage() {
696
                return baseLanguage;
697
        }
698

    
699
        /**
700
         * Sets the base language, the language considered the origin of
701
         * translations, which will be (possibly)
702
         * stored in a property file without language suffix
703
         * (ie: text.properties instead of text_es.properties).
704
         *
705
         * @param lang The base language to be set
706
         */
707
        public static void setBaseLanguage(String lang) {
708
                baseLanguage = lang;
709
                baseLocale = new Locale(baseLanguage);
710
        }
711

    
712

    
713
        public static Properties getAllTexts(Locale lang) {
714
                Properties texts = new Properties();
715
                getAllTexts(lang, null, texts);
716
                for (Iterator iterator = classLoaders.iterator(); iterator.hasNext();) {
717
                        getAllTexts(lang, (ClassLoader) iterator.next(), texts);
718
                }
719
                return texts;
720
        }
721

    
722
        private static void getAllTexts(Locale lang, ClassLoader classLoader,
723
                        Properties texts) {
724
                ClassLoader loader = classLoader == null ? Messages.class
725
                                .getClassLoader() : classLoader;
726

    
727
                for (Iterator iterator = resourceFamilies.iterator(); iterator
728
                                .hasNext();) {
729
                        String family = (String) iterator.next();
730
                        addResourceFamily(lang, texts, family, loader,
731
                                        "Messages.getAllTexts");
732
                }
733
        }
734

    
735
        
736
        public static Properties getTranslations(Locale locale) {
737
                Properties translations = new Properties();
738
                for( int curfamily=0; curfamily<familyDescriptors.size(); curfamily++) {
739
                     FamilyDescriptor family = (FamilyDescriptor) familyDescriptors.get(curfamily);
740
                     addResourceFamily(
741
                             locale,
742
                             translations,
743
                             family.family,
744
                             family.loader,
745
                             family.callerName);
746
                }
747
                return translations;
748
        }
749

    
750
        private static void addNotTranslatedKey(String key, String callerName, boolean log) {
751
            if (!notTranslatedKeys.contains(key)) {
752
                if( log ) {
753
                    logger.info("[" + callerName + "] Cannot find translation for key '" + key + "'.");
754
                }
755
                notTranslatedKeys.add(key);
756
            }
757
        }
758
        
759
        public static List getNotTranslatedKeys() {
760
            List l = new ArrayList(notTranslatedKeys);
761
            return l;
762
        }
763
}