1 |
package skrueger.i8n; |
2 |
|
3 |
import java.io.UnsupportedEncodingException; |
4 |
import java.util.Enumeration; |
5 |
import java.util.Locale; |
6 |
import java.util.PropertyResourceBundle; |
7 |
import java.util.ResourceBundle; |
8 |
|
9 |
/** |
10 |
* Credits to http://www.thoughtsabout.net/blog/archives/000044.html |
11 |
* |
12 |
* May 02, 2005 Quick and Dirty Hack for UTF-8 Support in ResourceBundle |
13 |
* |
14 |
* I just don't get why Sun folks didn't fix this in J2SE 1.5. By specification, |
15 |
* PropertyResourceBundles, or more exactly, the Properties files are Latin-1 |
16 |
* (i.e. ISO 8859-1) encoded:"When saving properties to a stream or loading them from a stream, the ISO 8859-1 character encoding is used. For characters that cannot be directly represented in this encoding, Unicode escapes are used; however, only a single 'u' character is allowed in an escape sequence. The native2ascii tool can be used to convert property files to and from other character encodings. " |
17 |
* . However, since all Latin-1 characters are in the same position in UTF-8 |
18 |
* encoding, I don't see a reason why they couldn't have just added support for |
19 |
* UTF-8 into the Properties class. |
20 |
* |
21 |
* While PropertyResourceBundle only has an implicit reference to the Properties |
22 |
* class, the problem is an overall bad design of ResourceBundle class |
23 |
* hierarchy. The super class ResourceBundle has two responsibilities: it acts |
24 |
* both as a super class and as a factory for loading ResourceBundles. The |
25 |
* ResourceBundle handles loading of PropertyResourceBundles that inherit from |
26 |
* ResourceBundle, and you can already smell a problem with this suspicous |
27 |
* implementation. Generally, the superclass should never need to know anything |
28 |
* about child classes implementing it. The getBundle() methods in it are |
29 |
* defined as final so there's no way to replace the the default implementation |
30 |
* of PropertyResourceBundle. Sun has two answer to this problem: either use |
31 |
* native2ascii tool to encode all double-byte characters in your Properties |
32 |
* file or implement your own ResourceBundle class. |
33 |
* |
34 |
* Using native2ascii by hooking it up with your Ant build as a task is fine, |
35 |
* but when you are developing and adding UTF-8 strings into your Properties |
36 |
* file, it's just an extra burden to run native2ascii after every change. On |
37 |
* Sun's forums, Craig McClanahan discusses how you could use your own |
38 |
* ResourceBundle class instead of Properties files to resolve the encoding |
39 |
* problem. But the issue with custom ResourceBundle classes is that they are |
40 |
* inherently different from PropertiesResourceBundle; you would need a custom |
41 |
* class per each locale you are supporting. Since ResourceBundle class handles |
42 |
* loading of the PropertyResourceBundles and the methods are marked final, you |
43 |
* are stuck with the Latin-1 encoding if you want to use Property files. |
44 |
* |
45 |
* The whole problem is stupid. Properties files should have supported UTF-8 in |
46 |
* the first place, but the change to support them could have been made at any |
47 |
* time after. Assuming UTF-8 as encoding when reading Latin-1 encoded file |
48 |
* wouldn't have broken anything: this backwards compatibility is the basic |
49 |
* reason why UTF-8 is so popular. All is not lost though; you could just use |
50 |
* your own ResourceBundle factory class for loading ResourceBundles and then |
51 |
* implement a UTF-8 PropertyResourceBundle class wrapper for UTF-8 support. |
52 |
* Here's a quick and dirty hack to do just that: |
53 |
*/ |
54 |
public abstract class Utf8ResourceBundle { |
55 |
|
56 |
public static final ResourceBundle getBundle(String baseName) { |
57 |
ResourceBundle bundle = ResourceBundle.getBundle(baseName); |
58 |
return createUtf8PropertyResourceBundle(bundle); |
59 |
} |
60 |
|
61 |
public static final ResourceBundle getBundle(String baseName, Locale locale) { |
62 |
ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale); |
63 |
return createUtf8PropertyResourceBundle(bundle); |
64 |
} |
65 |
|
66 |
public static ResourceBundle getBundle(String baseName, Locale locale, |
67 |
ClassLoader loader) { |
68 |
ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale); |
69 |
return createUtf8PropertyResourceBundle(bundle); |
70 |
} |
71 |
|
72 |
private static ResourceBundle createUtf8PropertyResourceBundle( |
73 |
ResourceBundle bundle) { |
74 |
if (!(bundle instanceof PropertyResourceBundle)) |
75 |
return bundle; |
76 |
|
77 |
return new Utf8PropertyResourceBundle((PropertyResourceBundle) bundle); |
78 |
} |
79 |
|
80 |
private static class Utf8PropertyResourceBundle extends ResourceBundle { |
81 |
PropertyResourceBundle bundle; |
82 |
|
83 |
private Utf8PropertyResourceBundle(PropertyResourceBundle bundle) { |
84 |
this.bundle = bundle; |
85 |
} |
86 |
|
87 |
/* |
88 |
* (non-Javadoc) |
89 |
* |
90 |
* @see java.util.ResourceBundle#getKeys() |
91 |
*/ |
92 |
public Enumeration getKeys() { |
93 |
return bundle.getKeys(); |
94 |
} |
95 |
|
96 |
/* |
97 |
* (non-Javadoc) |
98 |
* |
99 |
* @see java.util.ResourceBundle#handleGetObject(java.lang.String) |
100 |
*/ |
101 |
protected Object handleGetObject(String key) { |
102 |
String value = (String) bundle.handleGetObject(key); |
103 |
if (value == null) |
104 |
return null; |
105 |
try { |
106 |
return new String(value.getBytes("ISO-8859-1"), "UTF-8"); |
107 |
} catch (UnsupportedEncodingException e) { |
108 |
// Shouldn't fail - but should we still add logging message? |
109 |
return null; |
110 |
} |
111 |
} |
112 |
|
113 |
} |
114 |
} |