001: /**
002: *
003: */package clime.messadmin.i18n;
004:
005: import java.text.MessageFormat;
006: import java.util.Locale;
007: import java.util.MissingResourceException;
008: import java.util.ResourceBundle;
009:
010: import clime.messadmin.taglib.fmt.MessageTag;
011: import clime.messadmin.utils.StackIntrospector;
012:
013: /**
014: * ThreadLocal containing administration locale + i18n utilities.
015: * @author Cédrik LIME
016: * @since 4.1
017: */
018: public class I18NSupport {
019: private static final ThreadLocal/*<Locale>*/adminLocale = new ThreadLocal/*<Locale>*/();
020: /**
021: * Default Locale to use for the administration application, if none is requested: {@value}
022: */
023: public static final Locale DEFAULT_ADMIN_LOCALE = Locale.ENGLISH;
024: private static final Locale EMPTY_LOCALE = new Locale("", "");//$NON-NLS-1$//$NON-NLS-2$
025:
026: /**
027: *
028: */
029: private I18NSupport() {
030: super ();
031: }
032:
033: public static Locale getAdminLocale() {
034: Locale locale = (Locale) adminLocale.get();
035: return locale != null ? locale : DEFAULT_ADMIN_LOCALE;
036: }
037:
038: public static void setAdminLocale(Locale locale) {
039: adminLocale.set(locale);
040: }
041:
042: /**
043: * Gets the resource bundle with the given base name and preferred locale.
044: *
045: * This method calls java.util.ResourceBundle.getBundle(), but ignores
046: * its return value unless its locale represents an exact or language match
047: * with the given preferred locale.
048: *
049: * @param baseName the resource bundle base name
050: * @param locale the preferred locale
051: *
052: * @return the requested resource bundle, or <tt>null</tt> if no resource
053: * bundle with the given base name exists or if there is no exact- or
054: * language-match between the preferred locale and the locale of
055: * the bundle returned by java.util.ResourceBundle.getBundle().
056: *
057: * @see clime.messadmin.taglib.fmt.BundleTag#findMatch(String, Locale)
058: */
059: public static ResourceBundle getResourceBundle(String baseName,
060: Locale locale, ClassLoader cl) {
061: ResourceBundle match = null;
062:
063: try {
064: if (cl == null) {
065: cl = Thread.currentThread().getContextClassLoader();
066: }
067: ResourceBundle bundle = ResourceBundle.getBundle(baseName,
068: locale, cl);
069: Locale avail = bundle.getLocale();
070: if (locale.equals(avail)) {
071: // Exact match
072: match = bundle;
073: } else {
074: /*
075: * We have to make sure that the match we got is for
076: * the specified locale. The way ResourceBundle.getBundle()
077: * works, if a match is not found with (1) the specified locale,
078: * it tries to match with (2) the current default locale as
079: * returned by Locale.getDefault() or (3) the root resource
080: * bundle (basename).
081: * We must ignore any match that could have worked with (2).
082: * So if an exact match is not found, we make the following extra
083: * tests:
084: * - avail locale must be equal to preferred locale
085: * - avail country must be empty or equal to preferred country
086: * (the equality match might have failed on the variant)
087: */
088: if ("".equals(avail.getLanguage())//$NON-NLS-1$
089: || (locale.getLanguage().equals(
090: avail.getLanguage()) && ("".equals(avail.getCountry())//$NON-NLS-1$
091: || locale.getCountry().equals(
092: avail.getCountry())))) {
093: /*
094: * Language match.
095: * By making sure the available locale does not have a
096: * country and matches the preferred locale's language, we
097: * rule out "matches" based on the container's default
098: * locale. For example, if the preferred locale is
099: * "en-US", the container's default locale is "en-UK", and
100: * there is a resource bundle (with the requested base
101: * name) available for "en-UK", ResourceBundle.getBundle()
102: * will return it, but even though its language matches
103: * that of the preferred locale, we must ignore it,
104: * because matches based on the container's default locale
105: * are not portable across different containers with
106: * different default locales.
107: */
108: match = bundle;
109: }
110: if (match == null) {
111: /* Try to fetch (3) the root resource bundle (basename). This case is
112: * when (2) the current default locale as returned by Locale.getDefault()
113: * was used (ResourceBundle.getBundle() algorithm stopped at step 2).
114: */
115: bundle = ResourceBundle.getBundle(baseName,
116: EMPTY_LOCALE, cl);
117: avail = bundle.getLocale();
118: if ("".equals(avail.getLanguage())//$NON-NLS-1$
119: && "".equals(avail.getCountry())//$NON-NLS-1$
120: && "".equals(avail.getVariant())) {//$NON-NLS-1$
121: // we got a match
122: match = bundle;
123: }
124: }
125: }
126: } catch (MissingResourceException mre) {
127: }
128:
129: return match;
130: }
131:
132: public static ResourceBundle getResourceBundle(String baseName,
133: ClassLoader cl) {
134: return getResourceBundle(baseName, getAdminLocale(), cl);
135: }
136:
137: /**
138: * Retrieves the localized message corresponding to the given key, and
139: * performs parametric replacement using the arguments specified via
140: * <tt>args</tt>.
141: *
142: * <p>
143: * See the specification of {@link java.text.MessageFormat} for a
144: * description of how parametric replacement is implemented.
145: *
146: * <p>
147: * If no resource bundle with the given base name exists, or the given key
148: * is undefined in the resource bundle, the string "???<key>???" is
149: * returned, where "<key>" is replaced with the given key.
150: *
151: * @param baseName the resource bundle base name
152: * @param key the message key
153: * @param args the arguments for parametric replacement
154: *
155: * @return the localized message corresponding to the given key
156: */
157: public static String getLocalizedMessage(String baseName,
158: ClassLoader cl, String key, Object[] args) {
159: String message = MessageTag.UNDEFINED_KEY + key
160: + MessageTag.UNDEFINED_KEY;
161: ResourceBundle bundle = getResourceBundle(baseName, cl);
162: if (bundle != null) {
163: try {
164: message = bundle.getString(key);
165: if (args != null) {
166: MessageFormat formatter = new MessageFormat("");//$NON-NLS-1$
167: Locale locale = getAdminLocale();
168: if (locale != null) {
169: formatter.setLocale(locale);
170: }
171: formatter.applyPattern(message);
172: message = formatter.format(args);
173: }
174: } catch (MissingResourceException mre) {
175: }
176: }
177:
178: return message;
179: }
180:
181: public static String getLocalizedMessage(String baseName,
182: ClassLoader cl, String key) {
183: return getLocalizedMessage(baseName, cl, key, null);
184: }
185:
186: public static String getLocalizedMessage(ClassLoader cl,
187: String key, Object[] args) {
188: return getLocalizedMessage(StackIntrospector.getCallerClass()
189: .getName(), cl, key, args);
190: }
191:
192: public static String getLocalizedMessage(ClassLoader cl, String key) {
193: return getLocalizedMessage(StackIntrospector.getCallerClass()
194: .getName(), cl, key, null);
195: }
196:
197: public static String getLocalizedMessage(String key, Object[] args) {
198: return getLocalizedMessage(StackIntrospector.getCallerClass()
199: .getName(), null, key, args);
200: }
201:
202: public static String getLocalizedMessage(String key) {
203: return getLocalizedMessage(StackIntrospector.getCallerClass()
204: .getName(), null, key, null);
205: }
206: }
|