package com.limegroup.gnutella.i18n; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeMap; /** * @author Admin */ class LanguageLoader { /** @see LanguageInfo#getLink() */ static final String BUNDLE_NAME = "MessagesBundle"; /** @see LanguageInfo#getLink() */ static final String PROPS_EXT = ".properties"; /** @see LanguageInfo#getLink() */ static final String UTF8_EXT = ".UTF-8.txt"; private final Map/* <String code, LanguageInfo li> */langs; private final File lib; /** * @param directory */ LanguageLoader(File directory) { langs = new TreeMap(); lib = directory; } /** * List and load all available bundles and map them into the languages map. * Note that resources are not expanded here per base language, and not * cleaned here from extra keys (needed to support the resources "check" * option). * * @return the languages map (from complete locale codes to LocaleInfo) */ Map loadLanguages() { if (!lib.isDirectory()) throw new IllegalArgumentException("invalid lib: " + lib); String[] files = lib.list(); for (int i = 0; i < files.length; i++) { if (!files[i].startsWith(BUNDLE_NAME + "_") || !files[i].endsWith(PROPS_EXT) || files[i].startsWith(BUNDLE_NAME + "_en")) continue; /* See if a .UTF-8.txt file exists; if so, use that as the link. */ String linkFileName = files[i]; int idxProperties = linkFileName.indexOf(PROPS_EXT); File utf8 = new File(lib, linkFileName.substring(0, idxProperties) + UTF8_EXT); boolean skipUTF8LeadingBOM; if (utf8.exists()) { /* * properties files are normally read as streams of ISO-8859-1 * bytes but we want to check the UTF-8 source file. The non * ASCII characters in key values will be read as sequences of * Extended Latin 1 characters instead of the actual Unicode * character coded as Unicode escapes in the .properties file. * So they won't have the actual run-time value; however it * allows easier checking and validation here for messages * printed on the Console, that will output ISO-8859-1; the * result output still be interpretable as Unicode UTF-8. */ linkFileName = utf8.getName(); skipUTF8LeadingBOM = true; } else skipUTF8LeadingBOM = false; try { InputStream in = new FileInputStream( new File(lib, linkFileName/* files[i] */)); if (skipUTF8LeadingBOM) try { /* skip the three-bytes leading BOM */ /* * the leading BOM (U+FEFF), if present, is coded in * UTF-8 as three bytes 0xEF, 0xBB, 0xBF; they are not * part of a resource key. */ in.mark(3); if (in.read() != 0xEF || in.read() != 0xBB || in.read() != 0xBF) in.reset(); } catch (java.io.IOException ioe) { } loadFile(langs, in, linkFileName, files[i], skipUTF8LeadingBOM); } catch (FileNotFoundException fnfe) { // oh well. } } return langs; } /** * Constructs a list of each line in the default English properties file. * * @return a list of Line instances * @throws IOException * @see Line */ List /* of Line */getEnglishLines() throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader( new FileInputStream(new File(lib, BUNDLE_NAME + PROPS_EXT)), "ISO-8859-1")); List lines = new LinkedList(); String read; while ((read = reader.readLine()) != null) lines.add(new Line(read)); return lines; } /** * Retrieves the default properties. * * @return the loaded Properties * @throws IOException */ Properties getDefaultProperties() throws java.io.IOException { Properties p = new Properties(); InputStream in = new FileInputStream(new File(lib, BUNDLE_NAME + PROPS_EXT)); p.load(in); in.close(); return p; } /** * Retrieves the advanced keys. * * @return a the Set of Strings for the key names of advanced properties. * @throws IOException */ Set getAdvancedKeys() throws java.io.IOException { BufferedReader reader; reader = new BufferedReader(new InputStreamReader(new FileInputStream( new File(lib, BUNDLE_NAME + PROPS_EXT)), "ISO-8859-1")); String read; while ((read = reader.readLine()) != null && !read .startsWith("## TRANSLATION OF ALL ADVANCED RESOURCE STRINGS AFTER THIS LIMIT IS OPTIONAL")) ; StringBuffer sb = new StringBuffer(); while ((read = reader.readLine()) != null) { if (read.length() == 0 || read.charAt(0) == '#') continue; sb.append(read).append("\n"); } InputStream in = new ByteArrayInputStream(sb.toString().getBytes( "ISO-8859-1")); Properties p = new Properties(); p.load(in); in.close(); reader.close(); return p.keySet(); } /** * Extend variant resources from *already loaded* base languages. */ void extendVariantLanguages() { /* Extends missing resources with those from the base language */ for (Iterator i = langs.entrySet().iterator(); i.hasNext();) { final Map.Entry entry = (Map.Entry) i.next(); // final String code = (String)entry.getKey(); final LanguageInfo li = (LanguageInfo) entry.getValue(); final Properties props = li.getProperties(); if (li.isVariant()) { final LanguageInfo liBase = (LanguageInfo) langs.get(li .getBaseCode()); if (liBase != null) { /* Get a copy of base properties */ final Properties propsBase = new Properties(); propsBase.putAll(liBase.getProperties()); /* Remove properties already defined in the current locale */ propsBase.keySet().removeAll(props.keySet()); /* Add the remaining base properties to the current locale */ props.putAll(propsBase); } } } } /** * Iterates through all languages and retains only those within 'keys'. * * @param keys * a Set of String for key names to retain in properties. */ void retainKeys(Set keys) { /* Extends missing resources with those from the base language */ for (Iterator i = langs.entrySet().iterator(); i.hasNext();) { final Map.Entry entry = (Map.Entry) i.next(); // final String code = (String)entry.getKey(); final LanguageInfo li = (LanguageInfo) entry.getValue(); final Properties props = li.getProperties(); props.keySet().retainAll(keys); } } /** * Loads a single file into the languages map. */ private LanguageInfo loadFile(Map newlangs, InputStream in, String filename, String baseFileName, boolean isUTF8) { try { in = new BufferedInputStream(in); final Properties props = new Properties(); props.load(in); /* * note that the file is read in ISO-8859-1 only, even if it is * encoded with another charset. However, the Properties has its * unique legacy parser and we want to use it to make sure we use * the same syntax. So we'll need to correct the parsed values after * the file is read and interpreted as a set of properties * (keys,values). */ if (isUTF8) { // actually the file was UTF-8-encoded: convert bytes read // incorrectly as // ISO-8859-1 characters, into actual Unicode UTF-16 code units. for (Iterator i = props.entrySet().iterator(); i.hasNext();) { final Map.Entry entry = (Map.Entry) i.next(); final String key = (String) entry.getKey(); final String value = (String) entry.getValue(); byte[] bytes = null; try { bytes = value.getBytes("ISO-8859-1"); } catch (java.io.IOException ioe) { // should not occur } try { final String correctedValue = new String(bytes, "UTF-8"); if (!correctedValue.equals(value)) props.put(key, correctedValue); } catch (java.io.IOException ioe) { // may occur if .UTF-8.txt file was incorrectly encoded. System.err.println(ioe); } } } String lc = props.getProperty("LOCALE_LANGUAGE_CODE", ""); String cc = props.getProperty("LOCALE_COUNTRY_CODE", ""); String vc = props.getProperty("LOCALE_VARIANT_CODE", ""); String sc = props.getProperty("LOCALE_SCRIPT_CODE", ""); String ln = props.getProperty("LOCALE_LANGUAGE_NAME", lc); String cn = props.getProperty("LOCALE_COUNTRY_NAME", cc); String vn = props.getProperty("LOCALE_VARIANT_NAME", vc); String sn = props.getProperty("LOCALE_SCRIPT_NAME", sc); String dn = props.getProperty("LOCALE_ENGLISH_LANGUAGE_NAME", ln); String nsisName = props.getProperty("LOCALE_NSIS_NAME", ""); boolean rtl = props.getProperty("LAYOUT_RIGHT_TO_LEFT", "false") .equals("true"); LanguageInfo li = new LanguageInfo(lc, cc, vc, sc, ln, cn, vn, sn, dn, nsisName, rtl, filename, props, baseFileName); newlangs.put(li.getCode(), li); return li; } catch (IOException e) { // ignore. } finally { if (in != null) try { in.close(); } catch (IOException ioe) { } } return null; } }