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