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;
}
}