root / trunk / libraries / libInternationalization / src / org / gvsig / i18n / Messages.java @ 4736
History | View | Annotate | Download (19.9 KB)
1 |
package org.gvsig.i18n; |
---|---|
2 |
|
3 |
import java.io.*; |
4 |
import java.net.MalformedURLException; |
5 |
import java.net.URL; |
6 |
import java.net.URLClassLoader; |
7 |
import java.util.*; |
8 |
|
9 |
import org.apache.log4j.Logger; |
10 |
|
11 |
/**
|
12 |
* <p>This class offers some methods to provide internationalization services
|
13 |
* to other projects. All the methods are static.</p>
|
14 |
*
|
15 |
* <p>The most useful method is {@link #getText(String) getText(key)} (and family),
|
16 |
* which returns the translation associated
|
17 |
* with the provided key. The class must be initialized before getText can be
|
18 |
* used.</p>
|
19 |
*
|
20 |
* <p>The typical usage sequence would be:</p>
|
21 |
* <ul>
|
22 |
* <li>Add some locale to the prefered locales list: <code>Messages.addLocale(new Locale("es"))</code></li>
|
23 |
* <li>Add some resource file containing translations: <code>Messages.addResourceFamily("text", new File("."))</code></li>
|
24 |
* <li>And finaly getText can be used: <code>Messages.getText("aceptar")</code></li>
|
25 |
* </ul>
|
26 |
*
|
27 |
* <p>The resource files are Java properties files, which contain <code>key=translation</code>
|
28 |
* pairs, one
|
29 |
* pair per line. These files must be encoded using iso-8859-1 encoding, and unicode escaped
|
30 |
* sequences must be used to include characters outside the former encoding.
|
31 |
* There are several ways to specify the property file to load, see the different
|
32 |
* addResourceFamily methods for details.</p>
|
33 |
*
|
34 |
* @author Generalitat Valenciana and IVER Tecnolog?as de la Informaci?n S.A.
|
35 |
*
|
36 |
*/
|
37 |
public class Messages { |
38 |
private static Logger logger = Logger.getLogger("Messages"); |
39 |
private static String _CLASSNAME = "org.gvsig.i18n.Messages"; |
40 |
private static int _INITIALSIZE = 1000; |
41 |
|
42 |
private static Hashtable localeResources = new Hashtable(_INITIALSIZE, 2);; // contains the translations, indexed by key |
43 |
private static Vector _preferredLocales = new Vector(); // contains the ordered list of prefered languages/locales (class Locale) |
44 |
// private static Vector _availableLocales;
|
45 |
|
46 |
/**
|
47 |
* <p>Gets the localized message associated with the provided key.
|
48 |
* If the key is not in the dictionary, return the key and register
|
49 |
* the failure in the log.</p>
|
50 |
*
|
51 |
* <p>The <code>callerName</code> parameter is only
|
52 |
* used as a label when logging, so any String can be used. However, a
|
53 |
* meaningful String should be used, such as the name of the class requiring
|
54 |
* the translation services, in order to identify the source of the failure
|
55 |
* in the log.</p>
|
56 |
*
|
57 |
* @param key An String which identifies the translation that we want to get.
|
58 |
* @param callerName A symbolic name given to the caller of this method, to
|
59 |
* show it in the log if the key was not found
|
60 |
* @return an String with the message associated with the provided key.
|
61 |
* If the key is not in the dictionary, return the key. If the key
|
62 |
* is null, return null.
|
63 |
*/
|
64 |
public static String getText(String key, String callerName) { |
65 |
if (key==null) return null; |
66 |
String translation = (String) localeResources.get(key); |
67 |
if (translation!=null) |
68 |
return translation;
|
69 |
logger.warn(callerName+Messages.getText("Messages._no_se_encontro_la_traduccion_para", false)+key); |
70 |
return key;
|
71 |
} |
72 |
|
73 |
/**
|
74 |
* <p>Gets the localized message associated with the provided key.
|
75 |
* If the key is not in the dictionary, return the key and register
|
76 |
* the failure in the log.</p>
|
77 |
*
|
78 |
* @param key An String which identifies the translation that we want to get.
|
79 |
* @return an String with the message associated with the provided key.
|
80 |
* If the key is not in the dictionary, return the key. If the key
|
81 |
* is null, return null.
|
82 |
*/
|
83 |
public static String getText(String key) { |
84 |
return getText(key, ""); |
85 |
} |
86 |
|
87 |
/**
|
88 |
* <p>Gets the localized message associated with the provided key.
|
89 |
* If the key is not in the dictionary, it returns null and the failure
|
90 |
* is only registered in the log if the param log is true.</p>
|
91 |
*
|
92 |
* @param key An String which identifies the translation that we want to get.
|
93 |
* @param log Determines whether log a key failure or not
|
94 |
* @return an String with the message associated with the provided key,
|
95 |
* or null if the key is not in the dictionary.
|
96 |
*/
|
97 |
public static String getText(String key, boolean log) { |
98 |
if (key==null) return null; |
99 |
return (String) localeResources.get(key); |
100 |
} |
101 |
|
102 |
/**
|
103 |
* <p>Gets the localized message associated with the provided key.
|
104 |
* If the key is not in the dictionary, it returns null and the failure
|
105 |
* is only registered in the log if the param log is true.</p>
|
106 |
*
|
107 |
* @param key An String which identifies the translation that we want to get.
|
108 |
* @param callerName A symbolic name given to the caller of this method, to
|
109 |
* show it in the log if the key was not found
|
110 |
* @param log Determines whether log a key failure or not
|
111 |
* @return an String with the message associated with the provided key,
|
112 |
* or null if the key is not in the dictionary.
|
113 |
*/
|
114 |
public static String getText(String key, String callerName, boolean log) { |
115 |
if (key==null) return null; |
116 |
return (String) localeResources.get(key); |
117 |
} |
118 |
|
119 |
/**
|
120 |
* <p>Adds an additional family of resource files containing some translations.
|
121 |
* A family is a group of files with a common baseName.
|
122 |
* The file must be an iso-8859-1 encoded file, which can contain any unicode
|
123 |
* character using unicode escaped sequences, and following the syntax:
|
124 |
* <code>key1=value1
|
125 |
* key2=value2</code>
|
126 |
* where 'key1' is the key used to identify the string and must not
|
127 |
* contain the '=' symbol, and 'value1' is the associated translation.</p>
|
128 |
* <p<For example:</p>
|
129 |
* <code>cancel=Cancelar
|
130 |
* accept=Aceptar</code>
|
131 |
* <p>Only one pair key-value is allowed per line.</p>
|
132 |
*
|
133 |
* <p>The actual name of the resource file to load is determined using the rules
|
134 |
* explained in the class java.util.ResourceBundle. Summarizing, for each language
|
135 |
* in the specified preferred locales list it will try to load a file with
|
136 |
* the following structure: <code>family_locale.properties</code></p>
|
137 |
*
|
138 |
* <p>For example, if the preferred locales list contains {"fr", "es", "en"}, and
|
139 |
* the family name is "text", it will try to load the files "text_fr.properties",
|
140 |
* "text_es.properties" and finally "text_en.properties".</p>
|
141 |
*
|
142 |
* <p>Locales might be more specific, such us "es_AR" (meaning Spanish from Argentina)
|
143 |
* or "es_AR_linux" (meaning Linux system preferring Spanish from Argentina). In the
|
144 |
* later case, it will try to load "text_es_AR_linux.properties", then
|
145 |
* "text_es_AR.properties" if the former fails, and finally "text_es.properties".</p>
|
146 |
*
|
147 |
* <p>The directory used to locate the resource file is determining by using the
|
148 |
* getResource method from the provided ClassLoader.</p>
|
149 |
*
|
150 |
* @param family The family name (or base name) which is used to search
|
151 |
* actual properties files.
|
152 |
* @param loader A ClassLoader which is able to find a property file matching
|
153 |
* the specified family name and the preferred locales
|
154 |
* @see <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
|
155 |
*/
|
156 |
public static void addResourceFamily(String family, ClassLoader loader) { |
157 |
String currentKey;
|
158 |
Enumeration properties;
|
159 |
Enumeration languages = _preferredLocales.elements();
|
160 |
if (languages.hasMoreElements() == false) { |
161 |
// if it's empty, warn about that
|
162 |
logger.warn("No hay lista de idiomas preferidos. ?Quiz? olvid? inicializar la clase?");
|
163 |
} |
164 |
Locale lang;
|
165 |
ResourceBundle bundle = null; |
166 |
|
167 |
while (languages.hasMoreElements()) { // for each language |
168 |
try {
|
169 |
lang = (Locale) languages.nextElement();
|
170 |
bundle = ResourceBundle.getBundle(family, lang, loader);
|
171 |
if (bundle.getLocale().getLanguage().equals(lang.getLanguage()) || lang.getLanguage().equals("es")) { |
172 |
// we ensure we didn't get a fallback
|
173 |
// (except if lang == "es" -- spanish is the default language, so we accept a fallback for it)
|
174 |
|
175 |
properties = bundle.getKeys(); |
176 |
while (properties.hasMoreElements()) {
|
177 |
currentKey = (String) properties.nextElement();
|
178 |
if (!localeResources.containsKey(currentKey)) {
|
179 |
localeResources.put(currentKey, bundle.getString(currentKey)); |
180 |
} |
181 |
} |
182 |
} |
183 |
} |
184 |
catch (MissingResourceException ex) { |
185 |
logger.warn(_CLASSNAME+" MissingResourceException");
|
186 |
//logger.warn(_CLASSNAME+" ", ex);
|
187 |
} |
188 |
} |
189 |
} |
190 |
|
191 |
/**
|
192 |
* <p>Adds an additional family of resource files containing some translations.
|
193 |
* The search path to locate the files is provided by the dirList parameter.</p>
|
194 |
*
|
195 |
* <p>See {@link addResourceFamily(String, ClassLoader)} for a discussion about the
|
196 |
* format of the property files and the way to determine the candidat files
|
197 |
* to load. Note that those methods are different in the way to locate the
|
198 |
* candidat files. This method searches in the provided paths (<code>dirList</code>
|
199 |
* parameter), while the referred method searches using the getResource method
|
200 |
* of the provided ClassLoader.</p>
|
201 |
*
|
202 |
* @param family The family name (or base name) which is used to search
|
203 |
* actual properties files.
|
204 |
* @param dirList A list of search paths to locate the property files
|
205 |
* @throws MalformedURLException
|
206 |
* @see <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
|
207 |
*/
|
208 |
public static void addResourceFamily(String family, File[] dirList) throws MalformedURLException{ |
209 |
// use our own classloader
|
210 |
URL[] urls = new URL[dirList.length]; |
211 |
|
212 |
int i;
|
213 |
for (i=0; i<urls.length; i++) { |
214 |
urls[i] = dirList[i].toURL(); |
215 |
} |
216 |
|
217 |
ClassLoader loader = new PrivateClassLoader(urls); |
218 |
addResourceFamily(family, loader); |
219 |
} |
220 |
|
221 |
/**
|
222 |
* <p>Adds an additional family of resource files containing some translations.
|
223 |
* The search path to locate the files is provided by the dir parameter.</p>
|
224 |
*
|
225 |
* <p>See {@link addResourceFamily(String, ClassLoader)} for a discussion about the
|
226 |
* format of the property files and the way to determine the candidat files
|
227 |
* to load. Note that those methods are different in the way to locate the
|
228 |
* candidat files. This method searches in the provided path (<code>dir</code>
|
229 |
* parameter), while the referred method searches using the getResource method
|
230 |
* of the provided ClassLoader.</p>
|
231 |
*
|
232 |
* @param family The family name (or base name) which is used to search
|
233 |
* actual properties files.
|
234 |
* @param dir The search path to locate the property files
|
235 |
* @throws MalformedURLException
|
236 |
* @see <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
|
237 |
*/
|
238 |
public static void addResourceFamily(String family, File dir) throws MalformedURLException{ |
239 |
// use our own classloader
|
240 |
URL[] urls = new URL[1]; |
241 |
urls[0] = dir.toURL();
|
242 |
ClassLoader loader = new PrivateClassLoader(urls); |
243 |
addResourceFamily(family, loader); |
244 |
} |
245 |
|
246 |
|
247 |
/**
|
248 |
* <p>Adds an additional family of resource files containing some translations.
|
249 |
* The search path is determined by the getResource method from the
|
250 |
* provided ClassLoader.</p>
|
251 |
*
|
252 |
* <p>This method is identical to {@link addResourceFamily(String, ClassLoader)},
|
253 |
* except that it adds a <pode>callerName</code> parameter to show in the log.</p>
|
254 |
*
|
255 |
* <p>See {@link addResourceFamily(String, ClassLoader)} for a discussion about the
|
256 |
* format of the property files andthe way to determine the candidat files
|
257 |
* to load.</p>
|
258 |
*
|
259 |
* @param family The family name (or base name) which is used to search
|
260 |
* actual properties files.
|
261 |
* @param loader A ClassLoader which is able to find a property file matching
|
262 |
* the specified family name and the preferred locales
|
263 |
* @param callerName A symbolic name given to the caller of this method, to
|
264 |
* show it in the log if there is an error
|
265 |
* @see <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
|
266 |
*/
|
267 |
public static void addResourceFamily(String family, ClassLoader loader, String callerName) { |
268 |
String currentKey;
|
269 |
Enumeration properties;
|
270 |
Enumeration languages = _preferredLocales.elements();
|
271 |
if (languages.hasMoreElements() == false) { |
272 |
// if it's empty, warn about that
|
273 |
logger.warn("No hay lista de idiomas preferidos. ?Quiz? olvid? inicializar la clase?");
|
274 |
} |
275 |
Locale lang;
|
276 |
ResourceBundle bundle = null; |
277 |
|
278 |
while (languages.hasMoreElements()) { // for each language |
279 |
try {
|
280 |
lang = (Locale) languages.nextElement();
|
281 |
bundle = ResourceBundle.getBundle(family, lang, loader);
|
282 |
|
283 |
if (bundle.getLocale().getLanguage().equals(lang.getLanguage()) || lang.getLanguage().equals("es")) { |
284 |
// we ensure we didn't got a fallback
|
285 |
// (except if lang == "es" -- spanish is the default language, so we accept a fallback for it)
|
286 |
|
287 |
properties = bundle.getKeys(); |
288 |
while (properties.hasMoreElements()) {
|
289 |
currentKey = (String) properties.nextElement();
|
290 |
if (!localeResources.containsKey(currentKey)) {
|
291 |
localeResources.put(currentKey, bundle.getString(currentKey)); |
292 |
} |
293 |
} |
294 |
} |
295 |
else {
|
296 |
logger.warn(Messages.getText("Messages.las_traducciones_no_pudieron_ser_cargadas", false)+callerName+" -- "+lang.getLanguage()); |
297 |
} |
298 |
} |
299 |
catch (MissingResourceException ex) { |
300 |
logger.warn(callerName+" ", ex);
|
301 |
} |
302 |
} |
303 |
} |
304 |
|
305 |
/**
|
306 |
* <p>Adds an additional family of resource files containing some translations.</p>
|
307 |
*
|
308 |
* <p>This method is identical to {@link addResourceFamily(String, ClassLoader, String)},
|
309 |
* except that it uses the caller's class loader.</p>
|
310 |
*
|
311 |
* <p>See {@link addResourceFamily(String, ClassLoader)} for a discussion about the
|
312 |
* format of the property files and the way to determine the candidat files
|
313 |
* to load.</p>
|
314 |
*
|
315 |
* @param family The family name (or base name) which is used to search
|
316 |
* actual properties files.
|
317 |
* @param callerName A symbolic name given to the caller of this method, to
|
318 |
* show it in the log if there is an error. This is only used
|
319 |
* to show
|
320 |
* something meaningful in the log, so you can use any string
|
321 |
* @see <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
|
322 |
*/
|
323 |
public static void addResourceFamily(String family, String callerName) { |
324 |
String currentKey;
|
325 |
Enumeration properties;
|
326 |
Enumeration languages = _preferredLocales.elements();
|
327 |
if (languages.hasMoreElements() == false) { |
328 |
// if it's empty, warn about that
|
329 |
logger.warn("No hay lista de idiomas preferidos. ?Quiz? olvid? inicializar la clase?");
|
330 |
} |
331 |
Locale lang;
|
332 |
ResourceBundle bundle = null; |
333 |
|
334 |
while (languages.hasMoreElements()) { // for each language |
335 |
try {
|
336 |
lang = (Locale) languages.nextElement();
|
337 |
bundle = ResourceBundle.getBundle(family, lang);
|
338 |
|
339 |
if (bundle.getLocale().getLanguage()!=lang.getLanguage() && lang.getLanguage()!="es") { |
340 |
// if we got a fallback, discard it (we'll try the next language first)
|
341 |
// (except if lang == "es" -- spanish is the default language, so we accept a fallback for it
|
342 |
bundle = null;
|
343 |
} |
344 |
|
345 |
properties = bundle.getKeys(); |
346 |
while (properties.hasMoreElements()) {
|
347 |
currentKey = (String) properties.nextElement();
|
348 |
if (!localeResources.containsKey(currentKey)) {
|
349 |
localeResources.put(currentKey, bundle.getString(currentKey)); |
350 |
} |
351 |
} |
352 |
} |
353 |
catch (MissingResourceException ex) { |
354 |
logger.warn(callerName+" ", ex);
|
355 |
} |
356 |
} |
357 |
} |
358 |
|
359 |
|
360 |
/**
|
361 |
* Returns a vector containing the ordered list of prefered Locales
|
362 |
* Each element of the vector is a Locale object.
|
363 |
*
|
364 |
* @return a vector containing the ordered list of prefered Locales
|
365 |
* Each element of the vector is a Locale object.
|
366 |
*/
|
367 |
public static Vector getPreferredLocales() { |
368 |
return _preferredLocales;
|
369 |
} |
370 |
|
371 |
/**
|
372 |
* Sets the ordered list of preferred locales.
|
373 |
* Each element of the vector is a Locale object.
|
374 |
*
|
375 |
* @param preferredLocales a vector containing Locale objects.
|
376 |
* The Vector represents an ordered list of preferred locales
|
377 |
*/
|
378 |
public static void setPreferredLocales(Vector preferredLocales) { |
379 |
_preferredLocales = preferredLocales; |
380 |
} |
381 |
|
382 |
/**
|
383 |
* Adds a Locale at the end of the ordered list of preferred locales.
|
384 |
*
|
385 |
* @param lang A Locale object specifying the locale to add
|
386 |
*/
|
387 |
public static void addLocale(Locale lang) { |
388 |
if (_preferredLocales == null) { |
389 |
_preferredLocales = new Vector(); |
390 |
} |
391 |
_preferredLocales.add(lang); |
392 |
} |
393 |
|
394 |
/**
|
395 |
* Removes the specified Locale from the list of preferred locales.
|
396 |
*
|
397 |
* @param lang A Locale object specifying the locale to remove
|
398 |
*/
|
399 |
public static boolean removeLocale(Locale lang) { |
400 |
return _preferredLocales.remove(lang);
|
401 |
} |
402 |
|
403 |
/**
|
404 |
* Cleans the translation table (removes all the translations from memory).
|
405 |
*
|
406 |
* @param lang A Locale object specifying the locale to remove
|
407 |
*/
|
408 |
public static void removeResources() { |
409 |
localeResources = new Hashtable(_INITIALSIZE, 2);; // contains the translations, indexed by key |
410 |
} |
411 |
|
412 |
/**
|
413 |
* The number of translation keys which have been loaded till now
|
414 |
* (In other words: the number of available translation strings).
|
415 |
*/
|
416 |
protected static int size() { |
417 |
return localeResources.size();
|
418 |
} |
419 |
/**
|
420 |
* Searches the subdirectories of the provided directory, finding
|
421 |
* all the translation files, and constructing a list of available translations.
|
422 |
* It reports different country codes or variants, if available.
|
423 |
* For example, if there is an en_US translation and an en_GB translation, both
|
424 |
* locales will be present in the Vector.
|
425 |
*
|
426 |
* @return
|
427 |
*/
|
428 |
|
429 |
/**
|
430 |
*
|
431 |
* @return A Vector containing the available locales. Each element is a Locale object
|
432 |
*/
|
433 |
/*public static Vector getAvailableLocales() {
|
434 |
return _availableLocales;
|
435 |
}*/
|
436 |
|
437 |
/**
|
438 |
*
|
439 |
* @return A Vector containing the available languages. Each element is an String object
|
440 |
*/
|
441 |
/*public static Vector getAvailableLanguages() {
|
442 |
Vector availableLanguages = new Vector();
|
443 |
Locale lang;
|
444 |
Enumeration locales = _availableLocales.elements();
|
445 |
while (locales.hasMoreElements()) {
|
446 |
lang = (Locale) locales.nextElement();
|
447 |
availableLanguages.add(lang.getLanguage());
|
448 |
}
|
449 |
return availableLanguages;
|
450 |
}*/
|
451 |
} |
452 |
|
453 |
class PrivateClassLoader extends URLClassLoader { |
454 |
|
455 |
public PrivateClassLoader(URL[] urls) { |
456 |
super(urls);
|
457 |
} |
458 |
|
459 |
public URL getResource(String res) { |
460 |
try {
|
461 |
ArrayList resource = new ArrayList(); |
462 |
StringTokenizer st = new StringTokenizer(res, "\\/"); |
463 |
|
464 |
while (st.hasMoreTokens()) {
|
465 |
String token = st.nextToken();
|
466 |
resource.add(token); |
467 |
} |
468 |
URL ret = null; |
469 |
int currentUrl;
|
470 |
URL[] urls = getURLs(); |
471 |
|
472 |
for (currentUrl=0; currentUrl< urls.length; currentUrl++) { |
473 |
URL url = urls[currentUrl];
|
474 |
File file = new File(url.getFile()); |
475 |
if (url.getFile().endsWith("/")) |
476 |
ret = getResource(file, resource); |
477 |
} |
478 |
|
479 |
if (ret != null) { |
480 |
return ret;
|
481 |
} |
482 |
} catch (Exception e) { |
483 |
e.printStackTrace(); |
484 |
} |
485 |
|
486 |
return super.getResource(res); |
487 |
} |
488 |
|
489 |
/**
|
490 |
* Busca recursivamente el recurso res en el directorio base. res es una
|
491 |
* lista de String's con los directorios del path y base es el directorio
|
492 |
* a partir del cual se busca dicho recurso. En cada ejecuci?n del m?todo
|
493 |
* se toma el primer elemento de res y se busca dicho directorio en el
|
494 |
* directorio base. Si se encuentra, ser? el directorio base para una
|
495 |
* nueva llamada.
|
496 |
*
|
497 |
* @param base Directorio desde donde parte la b?squeda del recurso.
|
498 |
* @param res Lista de strings con el path del recurso que se quiere
|
499 |
* encontrar
|
500 |
*
|
501 |
* @return URL con el recurso
|
502 |
*/
|
503 |
private URL getResource(File base, List res) { |
504 |
File[] files = base.listFiles(); |
505 |
|
506 |
String parte = (String) res.get(0); |
507 |
|
508 |
for (int i = 0; i < files.length; i++) { |
509 |
if (files[i].getName().compareTo(parte) == 0) { |
510 |
if (res.size() == 1) { |
511 |
try {
|
512 |
return new URL("file:" + files[i].toString()); |
513 |
} catch (MalformedURLException e) { |
514 |
return null; |
515 |
} |
516 |
} else {
|
517 |
return getResource(files[i], res.subList(1, res.size())); |
518 |
} |
519 |
} |
520 |
} |
521 |
|
522 |
return null; |
523 |
} |
524 |
} |
525 |
|