1/* GNU gettext for Java 2 * Copyright (C) 2001, 2007 Free Software Foundation, Inc. 3 * 4 * This program is free software; you can redistribute it and/or modify it 5 * under the terms of the GNU Library General Public License as published 6 * by the Free Software Foundation; either version 2, or (at your option) 7 * any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 * Library General Public License for more details. 13 * 14 * You should have received a copy of the GNU Library General Public 15 * License along with this program; if not, write to the Free Software 16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 17 * USA. 18 */ 19 20package gnu.gettext; 21 22import java.lang.reflect.*; 23import java.util.*; 24 25/** 26 * This class implements the main GNU libintl functions in Java. 27 * <P> 28 * Using the GNU gettext approach, compiled message catalogs are normal 29 * Java ResourceBundle classes and are thus interoperable with standard 30 * ResourceBundle based code. 31 * <P> 32 * The main differences between the Sun ResourceBundle approach and the 33 * GNU gettext approach are: 34 * <UL> 35 * <LI>In the Sun approach, the keys are abstract textual shortcuts. 36 * In the GNU gettext approach, the keys are the English/ASCII version 37 * of the messages. 38 * <LI>In the Sun approach, the translation files are called 39 * "<VAR>Resource</VAR>_<VAR>locale</VAR>.properties" and have non-ASCII 40 * characters encoded in the Java 41 * <CODE>\</CODE><CODE>u<VAR>nnnn</VAR></CODE> syntax. Very few editors 42 * can natively display international characters in this format. In the 43 * GNU gettext approach, the translation files are called 44 * "<VAR>Resource</VAR>.<VAR>locale</VAR>.po" 45 * and are in the encoding the translator has chosen. Many editors 46 * can be used. There are at least three GUI translating tools 47 * (Emacs PO mode, KDE KBabel, GNOME gtranslator). 48 * <LI>In the Sun approach, the function 49 * <CODE>ResourceBundle.getString</CODE> throws a 50 * <CODE>MissingResourceException</CODE> when no translation is found. 51 * In the GNU gettext approach, the <CODE>gettext</CODE> function 52 * returns the (English) message key in that case. 53 * <LI>In the Sun approach, there is no support for plural handling. 54 * Even the most elaborate MessageFormat strings cannot provide decent 55 * plural handling. In the GNU gettext approach, we have the 56 * <CODE>ngettext</CODE> function. 57 * </UL> 58 * <P> 59 * To compile GNU gettext message catalogs into Java ResourceBundle classes, 60 * the <CODE>msgfmt</CODE> program can be used. 61 * 62 * @author Bruno Haible 63 */ 64public abstract class GettextResource extends ResourceBundle { 65 66 public static boolean verbose = false; 67 68 /** 69 * Like gettext(catalog,msgid), except that it returns <CODE>null</CODE> 70 * when no translation was found. 71 */ 72 private static String gettextnull (ResourceBundle catalog, String msgid) { 73 try { 74 return (String)catalog.getObject(msgid); 75 } catch (MissingResourceException e) { 76 return null; 77 } 78 } 79 80 /** 81 * Returns the translation of <VAR>msgid</VAR>. 82 * @param catalog a ResourceBundle 83 * @param msgid the key string to be translated, an ASCII string 84 * @return the translation of <VAR>msgid</VAR>, or <VAR>msgid</VAR> if 85 * none is found 86 */ 87 public static String gettext (ResourceBundle catalog, String msgid) { 88 String result = gettextnull(catalog,msgid); 89 if (result != null) 90 return result; 91 return msgid; 92 } 93 94 /** 95 * Like ngettext(catalog,msgid,msgid_plural,n), except that it returns 96 * <CODE>null</CODE> when no translation was found. 97 */ 98 private static String ngettextnull (ResourceBundle catalog, String msgid, long n) { 99 // The reason why we use so many reflective API calls instead of letting 100 // the GNU gettext generated ResourceBundles implement some interface, 101 // is that we want the generated ResourceBundles to be completely 102 // standalone, so that migration from the Sun approach to the GNU gettext 103 // approach (without use of plurals) is as straightforward as possible. 104 ResourceBundle origCatalog = catalog; 105 do { 106 // Try catalog itself. 107 if (verbose) 108 System.out.println("ngettext on "+catalog); 109 Method handleGetObjectMethod = null; 110 Method getParentMethod = null; 111 try { 112 handleGetObjectMethod = catalog.getClass().getMethod("handleGetObject", new Class[] { java.lang.String.class }); 113 getParentMethod = catalog.getClass().getMethod("getParent", new Class[0]); 114 } catch (NoSuchMethodException e) { 115 } catch (SecurityException e) { 116 } 117 if (verbose) 118 System.out.println("handleGetObject = "+(handleGetObjectMethod!=null)+", getParent = "+(getParentMethod!=null)); 119 if (handleGetObjectMethod != null 120 && Modifier.isPublic(handleGetObjectMethod.getModifiers()) 121 && getParentMethod != null) { 122 // A GNU gettext created class. 123 Method lookupMethod = null; 124 Method pluralEvalMethod = null; 125 try { 126 lookupMethod = catalog.getClass().getMethod("lookup", new Class[] { java.lang.String.class }); 127 pluralEvalMethod = catalog.getClass().getMethod("pluralEval", new Class[] { Long.TYPE }); 128 } catch (NoSuchMethodException e) { 129 } catch (SecurityException e) { 130 } 131 if (verbose) 132 System.out.println("lookup = "+(lookupMethod!=null)+", pluralEval = "+(pluralEvalMethod!=null)); 133 if (lookupMethod != null && pluralEvalMethod != null) { 134 // A GNU gettext created class with plural handling. 135 Object localValue = null; 136 try { 137 localValue = lookupMethod.invoke(catalog, new Object[] { msgid }); 138 } catch (IllegalAccessException e) { 139 e.printStackTrace(); 140 } catch (InvocationTargetException e) { 141 e.getTargetException().printStackTrace(); 142 } 143 if (localValue != null) { 144 if (verbose) 145 System.out.println("localValue = "+localValue); 146 if (localValue instanceof String) 147 // Found the value. It doesn't depend on n in this case. 148 return (String)localValue; 149 else { 150 String[] pluralforms = (String[])localValue; 151 long i = 0; 152 try { 153 i = ((Long) pluralEvalMethod.invoke(catalog, new Object[] { new Long(n) })).longValue(); 154 if (!(i >= 0 && i < pluralforms.length)) 155 i = 0; 156 } catch (IllegalAccessException e) { 157 e.printStackTrace(); 158 } catch (InvocationTargetException e) { 159 e.getTargetException().printStackTrace(); 160 } 161 return pluralforms[(int)i]; 162 } 163 } 164 } else { 165 // A GNU gettext created class without plural handling. 166 Object localValue = null; 167 try { 168 localValue = handleGetObjectMethod.invoke(catalog, new Object[] { msgid }); 169 } catch (IllegalAccessException e) { 170 e.printStackTrace(); 171 } catch (InvocationTargetException e) { 172 e.getTargetException().printStackTrace(); 173 } 174 if (localValue != null) { 175 // Found the value. It doesn't depend on n in this case. 176 if (verbose) 177 System.out.println("localValue = "+localValue); 178 return (String)localValue; 179 } 180 } 181 Object parentCatalog = catalog; 182 try { 183 parentCatalog = getParentMethod.invoke(catalog, new Object[0]); 184 } catch (IllegalAccessException e) { 185 e.printStackTrace(); 186 } catch (InvocationTargetException e) { 187 e.getTargetException().printStackTrace(); 188 } 189 if (parentCatalog != catalog) 190 catalog = (ResourceBundle)parentCatalog; 191 else 192 break; 193 } else 194 // Not a GNU gettext created class. 195 break; 196 } while (catalog != null); 197 // The end of chain of GNU gettext ResourceBundles is reached. 198 if (catalog != null) { 199 // For a non-GNU ResourceBundle we cannot access 'parent' and 200 // 'handleGetObject', so make a single call to catalog and all 201 // its parent catalogs at once. 202 Object value; 203 try { 204 value = catalog.getObject(msgid); 205 } catch (MissingResourceException e) { 206 value = null; 207 } 208 if (value != null) 209 // Found the value. It doesn't depend on n in this case. 210 return (String)value; 211 } 212 // Default: null. 213 return null; 214 } 215 216 /** 217 * Returns the plural form for <VAR>n</VAR> of the translation of 218 * <VAR>msgid</VAR>. 219 * @param catalog a ResourceBundle 220 * @param msgid the key string to be translated, an ASCII string 221 * @param msgid_plural its English plural form 222 * @return the translation of <VAR>msgid</VAR> depending on <VAR>n</VAR>, 223 * or <VAR>msgid</VAR> or <VAR>msgid_plural</VAR> if none is found 224 */ 225 public static String ngettext (ResourceBundle catalog, String msgid, String msgid_plural, long n) { 226 String result = ngettextnull(catalog,msgid,n); 227 if (result != null) 228 return result; 229 // Default: English strings and Germanic plural rule. 230 return (n != 1 ? msgid_plural : msgid); 231 } 232 233 /* The separator between msgctxt and msgid. */ 234 private static final String CONTEXT_GLUE = "\u0004"; 235 236 /** 237 * Returns the translation of <VAR>msgid</VAR> in the context of 238 * <VAR>msgctxt</VAR>. 239 * @param catalog a ResourceBundle 240 * @param msgctxt the context for the key string, an ASCII string 241 * @param msgid the key string to be translated, an ASCII string 242 * @return the translation of <VAR>msgid</VAR>, or <VAR>msgid</VAR> if 243 * none is found 244 */ 245 public static String pgettext (ResourceBundle catalog, String msgctxt, String msgid) { 246 String result = gettextnull(catalog,msgctxt+CONTEXT_GLUE+msgid); 247 if (result != null) 248 return result; 249 return msgid; 250 } 251 252 /** 253 * Returns the plural form for <VAR>n</VAR> of the translation of 254 * <VAR>msgid</VAR> in the context of <VAR>msgctxt</VAR>. 255 * @param catalog a ResourceBundle 256 * @param msgctxt the context for the key string, an ASCII string 257 * @param msgid the key string to be translated, an ASCII string 258 * @param msgid_plural its English plural form 259 * @return the translation of <VAR>msgid</VAR> depending on <VAR>n</VAR>, 260 * or <VAR>msgid</VAR> or <VAR>msgid_plural</VAR> if none is found 261 */ 262 public static String npgettext (ResourceBundle catalog, String msgctxt, String msgid, String msgid_plural, long n) { 263 String result = ngettextnull(catalog,msgctxt+CONTEXT_GLUE+msgid,n); 264 if (result != null) 265 return result; 266 // Default: English strings and Germanic plural rule. 267 return (n != 1 ? msgid_plural : msgid); 268 } 269} 270