KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > ca > commons > cbutil > CBLocalization


1 package com.ca.commons.cbutil;
2
3 import java.io.File JavaDoc;
4 import java.io.FileInputStream JavaDoc;
5 import java.text.MessageFormat JavaDoc;
6 import java.util.*;
7 import java.util.logging.Logger JavaDoc;
8 import java.util.logging.Level JavaDoc;
9
10
11 /**
12  * Reads a language translation file into a hashtable.
13  * Figures out the name of the translation file by
14  * the most specific description of the Locale. For example, <br>
15  * [language]_[country]_[variant].properties would be loaded before:<br>
16  * [language]_[country].properties would be loaded before:<br>
17  * [language].properties<br><br>
18  * Use the 'get' method to return the tranlated text.
19  *
20  * @author erstr01
21  */

22 public class CBLocalization
23 {
24     private final static Logger JavaDoc log = Logger.getLogger(CBLocalization.class.getName());
25
26     Locale locale = null;
27
28     MessageFormat JavaDoc messageFormatter = null;
29
30     Hashtable translations = null;
31
32     private boolean errorGiven = false; // This is a 'complain once' class - usually it's either working or it's not...
33

34     /*
35      * If the local language is english, don't print warning messages
36      * about missing translation files (it confuses soooo many people)
37      */

38     private boolean english = true;
39
40     /**
41      * This initialises the international text class.
42      *
43      * @param locale the locale to use.
44      * @param path the path where the language files are stored.
45      */

46     public CBLocalization(Locale locale, String JavaDoc path)
47     {
48         this.locale = locale;
49
50         translations = new Hashtable(500);
51
52         loadLanguageFile(path, locale);
53
54         if (!locale.getLanguage().equals("en"))
55             english = false;
56
57         messageFormatter = new MessageFormat JavaDoc("");
58         messageFormatter.setLocale(locale);
59     }
60
61     /**
62      * This attempts to get the localised version of a string.
63      * If anything goes wrong, it attempts to return the key
64      * string it was given - hence using a meaningfull key is
65      * a good harm minimization strategy.
66      */

67     public String JavaDoc get(String JavaDoc key)
68     {
69         if (key == null) // sanity check that a valid key has been passed.
70
{
71             return "null key";
72         }
73
74         if (translations == null || translations.size() == 0) // we never opened a file...
75
{
76             if (errorGiven == false) // only print error message once! (otherwise we'd print an
77
{ // error for every string in the program!)
78
if (!english) log.warning("Unable to translate (" + key + ") - can't find language file.");
79                 errorGiven = true;
80             }
81             return key; // try to keep on trucking using the (english) key phrase
82
}
83
84         try
85         {
86             String JavaDoc val = (String JavaDoc) translations.get(key); // return the translated word!
87
if (val == null) // this shouldn't happen, but can occur with an out-of-date (=incomplete) translation file.
88
{
89                 if (!english) log.warning("Can't find translation for '" + key + "' - returning '" + key + "' unchanged.");
90                 return key;
91             }
92             return val;
93         }
94         catch (MissingResourceException e)
95         {
96             return key; // couldn't find a translation, so return the keyword instead.
97
}
98     }
99
100     /**
101      * This attempts to get the localised version of a formatted string,
102      * inserting arguments as appropriate (see MessageFormat class
103      * for more details).
104      * If anything goes wrong, it attempts to return the key
105      * string it was given - hence using a meaningfull key is
106      * a good harm minimization strategy.
107      *
108      * @param key the format pattern; e.g. 'JXplorer has saved {0} ldif entries'.
109      * @param args the list of parameters to insert into the format pattern.
110      */

111     public String JavaDoc get(String JavaDoc key, ArrayList args)
112     {
113         return get(key, args.toArray());
114     }
115
116     /**
117      * This attempts to get the localised version of a formatted string,
118      * inserting arguments as appropriate (see MessageFormat class
119      * for more details).
120      * If anything goes wrong, it attempts to return the key
121      * string it was given - hence using a meaningfull key is
122      * a good harm minimization strategy.
123      *
124      * @param key the format pattern; e.g. 'JXplorer has saved {0} ldif entries'.
125      * @param args the list of parameters to insert into the format pattern.
126      */

127     public String JavaDoc get(String JavaDoc key, Object JavaDoc[] args)
128     {
129         if (key == null) // sanity check that a valid key has been passed.
130
{
131             return "null key";
132         }
133         String JavaDoc val = key;
134
135         if (translations == null || translations.size() == 0) // we never opened a file...
136
{
137             if (errorGiven == false) // only print error message once! (otherwise we'd print an
138
{ // error for every string in the program!)
139
if (!english) log.warning("Unable to translate (" + key + ") - can't find language file.");
140                 errorGiven = true;
141             }
142         }
143         else
144         {
145             try
146             {
147                 val = (String JavaDoc) translations.get(key); // return the translated word!
148
if (val == null) // this shouldn't happen, but can occur with an out-of-date (=incomplete) translation file.
149
{
150                     if (!english) log.warning("Can't find translation for (" + key + ") - returning unchanged.");
151                     val = key; // revert to english
152
}
153             }
154             catch (MissingResourceException e)
155             {
156                 val = key; // couldn't find a translation, so return the keyword instead.
157
}
158         }
159
160         return MessageFormat.format(val, args);
161     }
162
163     /**
164      * This method searches through all the valid permutations of the language
165      * files that are found under the path. It choses the file which is the
166      * most specific for the given locale. Then it loads the data (the translation
167      * strings) into a local data store (Hashtable).
168      *
169      * @param path the path where the language files are stored.
170      * @param locale a specific locale to use.
171      */

172     private void loadLanguageFile(String JavaDoc path, Locale locale)
173     {
174         Vector names = getLanguageFileNames(path, locale);
175
176         if (names == null)
177         {
178             log.warning("Names are null");
179             return;
180         }
181
182         //TE: the most specific language file is added to the Vector last, so start at the end and work backwards...
183
for (int i = names.size() - 1; i >= 0; i--)
184         {
185             // once a single file has been loaded, we're done...
186
if (loadData(new File JavaDoc(names.get(i).toString())) == true)
187                 return;
188         }
189
190         // couldn't succesfully load anything...
191
log.warning("Unable to load language file '" + path + "'");
192         return;
193     }
194
195     /**
196      * A file might be named 'en_AU.properties'. This method tries to figure out
197      * from the locale what the language file names are. The least specific file
198      * name is added to the list first and the most specific last. For example,
199      * the least specific file name would be...<br><br>
200      * [language].properties followed by<br>
201      * [language]_[country].properties followed by<br>
202      * [language]_[country]_[variant].properties<br><br>
203      * This method adds the file names to the list with and without the extension '.properties'
204      *
205      * @param path the path to the language files.
206      * @param locale the locale.
207      * @return a list of possible language file names.
208      */

209     private static Vector getLanguageFileNames(String JavaDoc path, Locale locale)
210     {
211         if (locale == null)
212         {
213             log.warning("Locale is null");
214             return null;
215         }
216
217         final Vector result = new Vector(8);
218         final String JavaDoc language = locale.getLanguage();
219         final int languageLength = language.length();
220         final String JavaDoc country = locale.getCountry();
221         final int countryLength = country.length();
222         final String JavaDoc variant = locale.getVariant();
223         final int variantLength = variant.length();
224         final StringBuffer JavaDoc temp = new StringBuffer JavaDoc(path);
225
226         if (languageLength + countryLength + variantLength == 0)
227         {
228             return result; //TE: The locale is "", "", "".
229
}
230
231         temp.append(language);
232
233         result.addElement(temp.toString() + ".properties"); //TE: file name is [language].properties
234
result.addElement(temp.toString()); //TE: file name is [language]
235

236         if (countryLength + variantLength == 0)
237         {
238             return result; //TE: The locale is for example "en", "", "".
239
}
240
241         temp.append('_');
242         temp.append(country);
243
244         result.addElement(temp.toString() + ".properties"); //TE: file name is [language]_[country].properties
245
result.addElement(temp.toString()); //TE: file name is [language]_[country]
246

247         if (variantLength == 0)
248         {
249             return result; //TE: The locale is for example "en", "AU", "".
250
}
251
252         temp.append('_');
253         temp.append(variant);
254
255         result.addElement(temp.toString() + ".properties"); //TE: file name is [language]_[country]_[variant].properties
256
result.addElement(temp.toString()); //TE: file name is [language]_[country]_[variant]
257

258         return result; //TE: The locale is for example "en", "AU", "variant".
259
}
260
261     /**
262      * This loads the data from a translation file, checking on the way
263      * what file format it is in. (UTF-8, 16bit unicode, or local encoding).
264      *
265      * @param file the File to read the data InputStream from.
266      * @return whether the load data operation was successfull, or whether
267      * it was interupted (for whatever reason; no file, error reading
268      * file, bad encoding, yadda yadda yadda).
269      */

270     private boolean loadData(File JavaDoc file)
271     {
272         try
273         {
274             if (file == null || !file.exists())
275                 return false;
276
277             log.info("Reading data from " + file.getAbsolutePath());
278
279             // First, slurp all the data from the input stream into a byte array...
280
byte[] data = CBUtility.readStream(new FileInputStream JavaDoc(file));
281
282             // Convert the byte array to a String using cunning auto-detecting encoding methods...
283
StringBuffer JavaDoc buffy = new StringBuffer JavaDoc(CBUtility.readI18NByteArray(data));
284
285             buffy.insert(buffy.length(), '\n'); //TE: make sure the last line has a '\n'!
286

287             // Load up the translations hashtable with the parsed data found in the string...
288
return parseData(buffy.toString());
289         }
290         catch (Exception JavaDoc e)
291         {
292             log.log(Level.WARNING, "Unable to read data from file '" + file.getAbsolutePath(), e);
293             return false;
294         }
295     }
296
297     /**
298      * Parses the byte array as per a normal resource file
299      * (i.e. looking for key/data pairs seperated by an
300      * unescaped '=' sign) after first converting the byte
301      * array into a String, using whichever language encoding
302      * (unicode16, utf8, locale-specific) seems appropriate.
303      *
304      * @param text the language file as a string.
305      * @return true if the language file has been loaded into the
306      * hashtable, false otherwise.
307      */

308     protected boolean parseData(String JavaDoc text)
309     {
310         int start = 0, end = 0;
311         while ((end = text.indexOf('\n', start)) != -1)
312         {
313             String JavaDoc line = text.substring(start, end);
314
315             line = line.trim();
316
317             if (line.startsWith("="))
318             {
319                 log.warning("Invalid entry in language file: '" + line + "'");
320             }
321             else if (line.length() != 0 && line.charAt(0) != '#') // ignore blank lines and commented lines.
322
{
323                 try
324                 {
325                     int equalPos = 0;
326
327                     do // skip through all escaped equals characters until we find a non-escaped one.
328
{
329                         equalPos = line.indexOf('=', equalPos + 1);
330                     }
331                     while (line.charAt(equalPos - 1) == '\\');
332
333                     String JavaDoc key = unescape(line.substring(0, equalPos)).trim();
334                     String JavaDoc trans = line.substring(equalPos + 1).trim();
335                     translations.put(key, trans);
336
337                 }
338                 catch (Exception JavaDoc e)
339                 {
340                     log.warning("Exception parsing data line '" + line + "' -> " + e);
341                 } // prob. array ex. - ignore this line.
342
}
343
344             start = end + 1;
345         }
346
347         // check if we added any new translations - if we did, then this was
348
// at least partially successfull.
349
boolean success = (translations.size() > 0);
350         if (success == false)
351             log.warning("ParseData unsuccessfull - no new data found");
352         return success;
353     }
354
355     /**
356      * Removes all escapes ('\?' -> '?') from a string.
357      * -> Not particularly efficient, but o.k. for short strings.
358      */

359     private String JavaDoc unescape(String JavaDoc escapeMe)
360     {
361         int pos = 0;
362         while ((pos = escapeMe.indexOf('\\', pos)) >= 0)
363             escapeMe = escapeMe.substring(0, pos) + escapeMe.substring(pos + 1);
364
365         return escapeMe;
366     }
367
368     /**
369      * Returns the translation keys.
370      *
371      * @return an Enumeration of all the known keys (usually translatable
372      * strings).
373      */

374     public Enumeration keys()
375     {
376         return translations.keys();
377     }
378
379     /**
380      * Returns the translation keys.
381      *
382      * @return an Enumeration of all the known keys (usually translatable
383      * strings).
384      */

385     public Enumeration getKeys()
386     {
387         return translations.keys();
388     }
389
390     /**
391      * Returns the object corresponding to a given key.
392      *
393      * @param key the original text to translate/look up
394      * @return the corresponding translation/object
395      */

396     public Object JavaDoc get(Object JavaDoc key)
397     {
398         return translations.get(key);
399     }
400
401     /**
402      * Returns the object corresponding to a given key.
403      *
404      * @param key the original text to translate/look up
405      * @return the corresponding translation/object
406      */

407     public Object JavaDoc getObject(Object JavaDoc key)
408     {
409         return translations.get(key);
410     }
411
412     /**
413      * Convenience class returning a particular object
414      * as a String. If the object <i>was</i> a String
415      * already it is passed back unchanged, otherwise
416      * 'toString()' is called on the object before returning.
417      * This class never throws a ClassCastException.
418      *
419      * @param key the original text to translate/look up
420      * @return the corresponding translation/object as a String
421      */

422     public String JavaDoc getString(String JavaDoc key)
423     {
424         if (key == null) return "";
425         Object JavaDoc o = translations.get(key);
426         if (o == null) return "";
427
428         return (o instanceof String JavaDoc) ? (String JavaDoc) o : o.toString();
429     }
430
431     /**
432      * Checks if the country code is a valid one according to
433      * Locale.getISOCountries().
434      *
435      * @param language the country code.
436      * @return if the newly created Locale or null if the country
437      * code is not valid
438      */

439     public static Locale createLocale(String JavaDoc language)
440     {
441         if (language == null)
442             return Locale.getDefault();
443
444         String JavaDoc[] langs = Locale.getISOLanguages();
445         for (int i = 0; i < langs.length; i++)
446             if (language.equalsIgnoreCase(langs[i]))
447                 return new Locale(langs[i]);
448
449         return Locale.getDefault();
450     }
451 }
452
Popular Tags