// License: GPL. Copyright 2007 by Immanuel Scholz and others
package org.openstreetmap.josm.tools;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.net.URL;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Locale;
import java.util.Vector;
import org.openstreetmap.josm.Main;
/**
* Internationalisation support.
*
* @author Immanuel.Scholz
*/
public class I18n {
private enum PluralMode { MODE_NOTONE, MODE_NONE, MODE_GREATERONE,
MODE_CS, MODE_AR, MODE_PL, MODE_RO, MODE_RU, MODE_SK, MODE_SL}
private static PluralMode pluralMode = PluralMode.MODE_NOTONE; /* english default */
private static HashMap<String, String> strings = null;
private static HashMap<String, String[]> pstrings = null;
private static HashMap<String, PluralMode> languages = new HashMap<String, PluralMode>();
/**
* Set by MainApplication. Changes here later will probably mess up everything, because
* many strings are already loaded.
*/
public static final String tr(String text, Object... objects) {
return MessageFormat.format(gettext(text, null), objects);
}
public static final String tr(String text) {
return MessageFormat.format(gettext(text, null), (Object)null);
}
public static final String trc(String ctx, String text) {
return MessageFormat.format(gettext(text, ctx), (Object)null);
}
/* NOTE: marktr does NOT support context strings - use marktrc instead */
public static final String marktr(String text) {
return text;
}
public static final String marktrc(String context, String text) {
return text;
}
public static final String trn(String text, String pluralText, long n, Object... objects) {
return MessageFormat.format(gettextn(text, pluralText, null, n), objects);
}
public static final String trn(String text, String pluralText, long n) {
return MessageFormat.format(gettextn(text, pluralText, null, n), (Object)null);
}
public static final String trnc(String ctx, String text, String pluralText, long n, Object... objects) {
return MessageFormat.format(gettextn(text, pluralText, ctx, n), objects);
}
public static final String trnc(String ctx, String text, String pluralText, long n) {
return MessageFormat.format(gettextn(text, pluralText, ctx, n), (Object)null);
}
private static final String gettext(String text, String ctx)
{
int i;
if(ctx == null && text.startsWith("_:") && (i = text.indexOf("\n")) >= 0)
{
ctx = text.substring(2,i-1);
text = text.substring(i+1);
}
if(strings != null)
{
String trans = strings.get(ctx == null ? text : "_:"+ctx+"\n"+text);
if(trans != null)
return trans;
}
if(pstrings != null) {
String[] trans = pstrings.get(ctx == null ? text : "_:"+ctx+"\n"+text);
if(trans != null)
return trans[0];
}
return text;
}
private static final String gettextn(String text, String plural, String ctx, long num)
{
int i;
if(ctx == null && text.startsWith("_:") && (i = text.indexOf("\n")) >= 0)
{
ctx = text.substring(2,i-1);
text = text.substring(i+1);
}
if(pstrings != null)
{
i = pluralEval(num);
String[] trans = pstrings.get(ctx == null ? text : "_:"+ctx+"\n"+text);
if(trans != null && trans.length > i)
return trans[i];
}
return num == 1 ? text : plural;
}
/**
* Get a list of all available JOSM Translations.
* @return an array of locale objects.
*/
public static final Locale[] getAvailableTranslations() {
Vector<Locale> v = new Vector<Locale>();
if(Main.class.getResource("/data/en.lang") != null)
{
for (String loc : languages.keySet()) {
if(Main.class.getResource("/data/"+loc+".lang") != null) {
int i = loc.indexOf('_');
if (i > 0) {
v.add(new Locale(loc.substring(0, i), loc.substring(i + 1)));
} else {
v.add(new Locale(loc));
}
}
}
}
v.add(Locale.ENGLISH);
Locale[] l = new Locale[v.size()];
l = v.toArray(l);
Arrays.sort(l, new Comparator<Locale>() {
public int compare(Locale o1, Locale o2) {
return o1.toString().compareTo(o2.toString());
}
});
return l;
}
public static void init()
{
languages.put("ar", PluralMode.MODE_AR);
languages.put("bg", PluralMode.MODE_NOTONE);
languages.put("cs", PluralMode.MODE_CS);
languages.put("da", PluralMode.MODE_NOTONE);
languages.put("de", PluralMode.MODE_NOTONE);
languages.put("el", PluralMode.MODE_NOTONE);
languages.put("en_AU", PluralMode.MODE_NOTONE);
languages.put("en_GB", PluralMode.MODE_NOTONE);
languages.put("es", PluralMode.MODE_NOTONE);
languages.put("et", PluralMode.MODE_NOTONE);
languages.put("fi", PluralMode.MODE_NOTONE);
languages.put("fr", PluralMode.MODE_GREATERONE);
languages.put("gl", PluralMode.MODE_NOTONE);
languages.put("is", PluralMode.MODE_NOTONE);
languages.put("it", PluralMode.MODE_NOTONE);
languages.put("iw_IL", PluralMode.MODE_NOTONE);
languages.put("ja", PluralMode.MODE_NONE);
languages.put("nb", PluralMode.MODE_NOTONE);
languages.put("nl", PluralMode.MODE_NOTONE);
languages.put("pl", PluralMode.MODE_PL);
languages.put("pt_BR", PluralMode.MODE_GREATERONE);
languages.put("ro", PluralMode.MODE_RO);
languages.put("ru", PluralMode.MODE_RU);
languages.put("sk", PluralMode.MODE_SK);
languages.put("sl", PluralMode.MODE_SL);
languages.put("sv", PluralMode.MODE_NOTONE);
languages.put("tr", PluralMode.MODE_NONE);
languages.put("zh_TW", PluralMode.MODE_NONE);
/* try initial language settings, may be changed later again */
if(!load(Locale.getDefault().toString())) {
Locale.setDefault(Locale.ENGLISH);
}
}
private static boolean load(String l)
{
if(l.equals("en") || l.equals("en_US"))
{
strings = null;
pstrings = null;
pluralMode = PluralMode.MODE_NOTONE;
return true;
}
URL en = Main.class.getResource("/data/en.lang");
if(en == null)
return false;
URL tr = Main.class.getResource("/data/"+l+".lang");
if(tr == null)
{
int i = l.indexOf('_');
if (i > 0) {
l = l.substring(0, i);
}
tr = Main.class.getResource("/data/"+l+".lang");
if(tr == null)
return false;
}
HashMap<String, String> s = new HashMap<String, String>();
HashMap<String, String[]> p = new HashMap<String, String[]>();
/* file format:
for all single strings:
{
unsigned short (2 byte) stringlength
string
}
unsigned short (2 byte) 0xFFFF (marks end of single strings)
for all multi strings:
{
unsigned char (1 byte) stringcount
for stringcount
unsigned short (2 byte) stringlength
string
}
*/
try
{
InputStream ens = new BufferedInputStream(en.openStream());
InputStream trs = new BufferedInputStream(tr.openStream());
byte[] enlen = new byte[2];
byte[] trlen = new byte[2];
boolean multimode = false;
byte[] str = new byte[4096];
for(;;)
{
if(multimode)
{
int ennum = ens.read();
int trnum = trs.read();
if((ennum == -1 && trnum != -1) || (ennum != -1 && trnum == -1)) /* files do not match */
return false;
if(ennum == -1) {
break;
}
String[] enstrings = new String[ennum];
String[] trstrings = new String[trnum];
for(int i = 0; i < ennum; ++i)
{
int val = ens.read(enlen);
if(val != 2) /* file corrupt */
return false;
val = (enlen[0] < 0 ? 256+enlen[0]:enlen[0])*256+(enlen[1] < 0 ? 256+enlen[1]:enlen[1]);
if(val > str.length) {
str = new byte[val];
}
int rval = ens.read(str, 0, val);
if(rval != val) /* file corrupt */
return false;
enstrings[i] = new String(str, 0, val, "utf-8");
}
for(int i = 0; i < trnum; ++i)
{
int val = trs.read(trlen);
if(val != 2) /* file corrupt */
return false;
val = (trlen[0] < 0 ? 256+trlen[0]:trlen[0])*256+(trlen[1] < 0 ? 256+trlen[1]:trlen[1]);
if(val > str.length) {
str = new byte[val];
}
int rval = trs.read(str, 0, val);
if(rval != val) /* file corrupt */
return false;
trstrings[i] = new String(str, 0, val, "utf-8");
}
if(trnum > 0) {
p.put(enstrings[0], trstrings);
}
}
else
{
int enval = ens.read(enlen);
int trval = trs.read(trlen);
if(enval != trval) /* files do not match */
return false;
if(enval == -1) {
break;
}
if(enval != 2) /* files corrupt */
return false;
enval = (enlen[0] < 0 ? 256+enlen[0]:enlen[0])*256+(enlen[1] < 0 ? 256+enlen[1]:enlen[1]);
trval = (trlen[0] < 0 ? 256+trlen[0]:trlen[0])*256+(trlen[1] < 0 ? 256+trlen[1]:trlen[1]);
if(enval == 0xFFFF)
{
multimode = true;
if(trval != 0xFFFF) /* files do not match */
return false;
}
else
{
if(enval > str.length) {
str = new byte[enval];
}
if(trval > str.length) {
str = new byte[trval];
}
int val = ens.read(str, 0, enval);
if(val != enval) /* file corrupt */
return false;
String enstr = new String(str, 0, enval, "utf-8");
if(trval != 0)
{
val = trs.read(str, 0, trval);
if(val != trval) /* file corrupt */
return false;
String trstr = new String(str, 0, trval, "utf-8");
s.put(enstr, trstr);
}
}
}
}
}
catch(Exception e)
{
return false;
}
if(!s.isEmpty() && languages.containsKey(l))
{
strings = s;
pstrings = p;
pluralMode = languages.get(l);
return true;
}
return false;
}
/**
* Sets the default locale (see {@see Locale#setDefault(Locale)} to the local
* given by <code>localName</code>.
*
* Ignored if localName is null. If the locale with name <code>localName</code>
* isn't found the default local is set to <tt>en</tt> (english).
*
* @param localeName the locale name. Ignored if null.
*/
public static void set(String localeName){
if (localeName != null) {
Locale l;
if (localeName.equals("he")) {
localeName = "iw_IL";
}
int i = localeName.indexOf('_');
if (i > 0) {
l = new Locale(localeName.substring(0, i), localeName.substring(i + 1));
} else {
l = new Locale(localeName);
}
if(load(localeName)) {
Locale.setDefault(l);
} else {
if (!l.getLanguage().equals("en")) {
System.out.println(tr("Unable to find translation for the locale {0}. Reverting to {1}.",
l.getDisplayName(), Locale.getDefault().getDisplayName()));
} else {
strings = null;
pstrings = null;
}
}
}
}
private static int pluralEval(long n)
{
switch(pluralMode)
{
case MODE_NOTONE: /* bg, da, de, el, en, en_GB, es, et, fi, gl, is, it, iw_IL, nb, nl, sv */
return ((n != 1) ? 1 : 0);
case MODE_NONE: /* ja, tr, zh_TW */
return 0;
case MODE_GREATERONE: /* fr, pt_BR */
return ((n > 1) ? 1 : 0);
case MODE_CS:
return ((n == 1) ? 0 : (((n >= 2) && (n <= 4)) ? 1 : 2));
case MODE_AR:
return ((n == 0) ? 0 : ((n == 1) ? 1 : ((n == 2) ? 2 : ((((n % 100) >= 3)
&& ((n % 100) <= 10)) ? 3 : ((((n % 100) >= 11) && ((n % 100) <= 99)) ? 4 : 5)))));
case MODE_PL:
return ((n == 1) ? 0 : (((((n % 10) >= 2) && ((n % 10) <= 4))
&& (((n % 100) < 10) || ((n % 100) >= 20))) ? 1 : 2));
case MODE_RO:
return ((n == 1) ? 0 : ((((n % 100) > 19) || (((n % 100) == 0) && (n != 0))) ? 2 : 1));
case MODE_RU:
return ((((n % 10) == 1) && ((n % 100) != 11)) ? 0 : (((((n % 10) >= 2)
&& ((n % 10) <= 4)) && (((n % 100) < 10) || ((n % 100) >= 20))) ? 1 : 2));
case MODE_SK:
return ((n == 1) ? 1 : (((n >= 2) && (n <= 4)) ? 2 : 0));
case MODE_SL:
return (((n % 100) == 1) ? 1 : (((n % 100) == 2) ? 2 : ((((n % 100) == 3)
|| ((n % 100) == 4)) ? 3 : 0)));
}
return 0;
}
}