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 @ 44881

History | View | Annotate | Download (29.4 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
import org.gvsig.tools.util.URLUtils;
43

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

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

    
91
    /* Each entry will contain a hashmap with translations. Each hasmap
92
     * contains the translations for one language, indexed by the
93
     * translation key. The translations for language (i) in the preferred locales
94
     * list are contained in the position (i) of the localeResources list */
95
    private static ArrayList localeResources = new ArrayList();
96
    private static ArrayList preferredLocales = new ArrayList(); // contains the ordered list of prefered languages/locales (class Locale)
97
    private static Set notTranslatedKeys = new HashSet();
98

    
99

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
378

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

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

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

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

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

    
423
                        addResourceFamily(lang, translations, family, loader, callerName);
424
                }
425
        }
426
        
427
  private static void addResourceFamily(Locale lang, Properties translations,
428
          String family, ClassLoader loader, String callerName) {
429
    logger.debug("addResourceFamily " + lang.toString() + ", " + family + ", " + loader.toString());
430

    
431
    Properties properties = new Properties();
432
    String langCode = lang.toString();
433
    String resource = family.replace('.', '/') + "_" + langCode + ".properties";
434
    URL resourceURL = loader.getResource(resource);
435
//    InputStream is = loader.getResourceAsStream(resource);
436
    InputStream is = URLUtils.openStream(resourceURL);
437
    if (is == null && langCode.contains("_")) {
438
      try {
439
        langCode = langCode.split("_")[0];
440
        resource = family.replace('.', '/') + "_" + langCode + ".properties";
441
        resourceURL = loader.getResource(resource);
442
//        is = loader.getResourceAsStream(resource);
443
        is = URLUtils.openStream(resourceURL);
444
        if (is == null) {
445
          resource = family.replace('.', '/') + ".properties";
446
          resourceURL = loader.getResource(resource);
447
//          is = loader.getResourceAsStream(resource);
448
          is = URLUtils.openStream(resourceURL);
449
        }
450
      } catch (Exception ex) {
451
        // Do nothing, is are null and are handled later
452
      }
453
    }
454
    if (is != null) {
455
      try {
456
        properties.load(is);
457
      } catch (IOException e) {
458
      }
459
    } else if (lang.equals(baseLocale)) {
460
      // try also "text.properties" for the base language
461
      resource = family.replace('.', '/') + ".properties";
462
      resourceURL = loader.getResource(resource);
463
      is = URLUtils.openStream(resourceURL);
464
//      is = loader.getResourceAsStream(family.replace('.', '/') + ".properties");
465

    
466
      if (is != null) {
467
        try {
468
          properties.load(is);
469
        } catch (IOException e) {
470
        }
471
      }
472

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

    
485
  }
486

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

    
509

    
510
        /**
511
         * Returns an ArrayList containing the ordered list of prefered Locales
512
         * Each element of the ArrayList is a Locale object.
513
         *
514
         * @return an ArrayList containing the ordered list of prefered Locales
515
         * Each element of the ArrayList is a Locale object.
516
         */
517
        public static ArrayList getPreferredLocales() {
518
                return preferredLocales;
519
        }
520

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

    
545
                // add the new locales now
546
                for (int numLocale=0; numLocale < preferredLocalesList.size(); numLocale++) {
547
                        addLocale((Locale) preferredLocalesList.get(numLocale));
548
                }
549
        }
550

    
551
        public static Locale getCurrentLocale() {
552
            return currentLocale;
553
        }
554

    
555
        /**
556
         * 
557
         * @param locale
558
         * @deprecated  use setCurrentLocale(Locale locale, Locale alternatives[]) or LocaleManager.setCurrentLocale
559
         */
560
        public static void setCurrentLocale(Locale locale) {
561
            Locale alternatives[] = null;
562

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

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

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

    
649
        /**
650
         * Cleans the translation tables (removes all the translations from memory).
651
         */
652
        public static void removeResources() {
653
                for (int numLocale=0; numLocale<localeResources.size(); numLocale++) {
654
                        ((Properties)localeResources.get(numLocale)).clear();
655
                }
656
        }
657

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

    
673
        protected static Set keySet(Locale lang) {
674
                int numLocale = preferredLocales.indexOf(lang);
675
                if (numLocale!=-1) {
676
                        return ((Properties)localeResources.get(numLocale)).keySet();
677
                } else {
678
                        return null;
679
                }
680
        }
681

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

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

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

    
717

    
718
        public static Properties getAllTexts(Locale lang) {
719
                Properties texts = new Properties();
720
                getAllTexts(lang, null, texts);
721
                for (Iterator iterator = classLoaders.iterator(); iterator.hasNext();) {
722
                        getAllTexts(lang, (ClassLoader) iterator.next(), texts);
723
                }
724
                return texts;
725
        }
726

    
727
        private static void getAllTexts(Locale lang, ClassLoader classLoader,
728
                        Properties texts) {
729
                ClassLoader loader = classLoader == null ? Messages.class
730
                                .getClassLoader() : classLoader;
731

    
732
                for (Iterator iterator = resourceFamilies.iterator(); iterator
733
                                .hasNext();) {
734
                        String family = (String) iterator.next();
735
                        addResourceFamily(lang, texts, family, loader,
736
                                        "Messages.getAllTexts");
737
                }
738
        }
739

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

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