source: josm/trunk/src/org/openstreetmap/josm/tools/I18n.java@ 2841

Last change on this file since 2841 was 2841, checked in by mjulius, 14 years ago

treat all translatable strings the same in all tr*() implementations

  • Property svn:eol-style set to native
File size: 14.5 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.tools;
3
4import java.io.BufferedInputStream;
5import java.io.InputStream;
6import java.net.URL;
7import java.text.MessageFormat;
8import java.util.Arrays;
9import java.util.Comparator;
10import java.util.HashMap;
11import java.util.Locale;
12import java.util.Vector;
13
14import org.openstreetmap.josm.Main;
15
16/**
17 * Internationalisation support.
18 *
19 * @author Immanuel.Scholz
20 */
21public class I18n {
22 private enum PluralMode { MODE_NOTONE, MODE_NONE, MODE_GREATERONE,
23 MODE_CS, MODE_AR, MODE_PL, MODE_RO, MODE_RU, MODE_SK, MODE_SL}
24 private static PluralMode pluralMode = PluralMode.MODE_NOTONE; /* english default */
25 private static HashMap<String, String> strings = null;
26 private static HashMap<String, String[]> pstrings = null;
27 private static HashMap<String, PluralMode> languages = new HashMap<String, PluralMode>();
28
29 /**
30 * Set by MainApplication. Changes here later will probably mess up everything, because
31 * many strings are already loaded.
32 */
33 public static final String tr(String text, Object... objects) {
34 return MessageFormat.format(gettext(text, null), objects);
35 }
36
37 public static final String tr(String text) {
38 return MessageFormat.format(gettext(text, null), (Object)null);
39 }
40
41 public static final String trc(String ctx, String text) {
42 return MessageFormat.format(gettext(text, ctx), (Object)null);
43 }
44
45 /* NOTE: marktr does NOT support context strings - use marktrc instead */
46 public static final String marktr(String text) {
47 return text;
48 }
49
50 public static final String marktrc(String context, String text) {
51 return text;
52 }
53
54 public static final String trn(String text, String pluralText, long n, Object... objects) {
55 return MessageFormat.format(gettextn(text, pluralText, null, n), objects);
56 }
57
58 public static final String trn(String text, String pluralText, long n) {
59 return MessageFormat.format(gettextn(text, pluralText, null, n), (Object)null);
60 }
61
62 public static final String trnc(String ctx, String text, String pluralText, long n, Object... objects) {
63 return MessageFormat.format(gettextn(text, pluralText, ctx, n), objects);
64 }
65
66 public static final String trnc(String ctx, String text, String pluralText, long n) {
67 return MessageFormat.format(gettextn(text, pluralText, ctx, n), (Object)null);
68 }
69
70 private static final String gettext(String text, String ctx)
71 {
72 int i;
73 if(ctx == null && text.startsWith("_:") && (i = text.indexOf("\n")) >= 0)
74 {
75 ctx = text.substring(2,i-1);
76 text = text.substring(i+1);
77 }
78 if(strings != null)
79 {
80 String trans = strings.get(ctx == null ? text : "_:"+ctx+"\n"+text);
81 if(trans != null)
82 return trans;
83 }
84 return text;
85 }
86
87 private static final String gettextn(String text, String plural, String ctx, long num)
88 {
89 int i;
90 if(ctx == null && text.startsWith("_:") && (i = text.indexOf("\n")) >= 0)
91 {
92 ctx = text.substring(2,i-1);
93 text = text.substring(i+1);
94 }
95 if(pstrings != null)
96 {
97 i = pluralEval(num);
98 String[] trans = pstrings.get(ctx == null ? text : "_:"+ctx+"\n"+text);
99 if(trans != null && trans.length >= i)
100 return trans[i];
101 }
102
103 return num == 1 ? text : plural;
104 }
105
106 /**
107 * Get a list of all available JOSM Translations.
108 * @return an array of locale objects.
109 */
110 public static final Locale[] getAvailableTranslations() {
111 Vector<Locale> v = new Vector<Locale>();
112 if(Main.class.getResource("/data/en.lang") != null)
113 {
114 for (String loc : languages.keySet()) {
115 if(Main.class.getResource("/data/"+loc+".lang") != null) {
116 int i = loc.indexOf('_');
117 if (i > 0) {
118 v.add(new Locale(loc.substring(0, i), loc.substring(i + 1)));
119 } else {
120 v.add(new Locale(loc));
121 }
122 }
123 }
124 }
125 v.add(Locale.ENGLISH);
126 Locale[] l = new Locale[v.size()];
127 l = v.toArray(l);
128 Arrays.sort(l, new Comparator<Locale>() {
129 public int compare(Locale o1, Locale o2) {
130 return o1.toString().compareTo(o2.toString());
131 }
132 });
133 return l;
134 }
135
136 public static void init()
137 {
138 languages.put("ar", PluralMode.MODE_AR);
139 languages.put("bg", PluralMode.MODE_NOTONE);
140 languages.put("cs", PluralMode.MODE_CS);
141 languages.put("da", PluralMode.MODE_NOTONE);
142 languages.put("de", PluralMode.MODE_NOTONE);
143 languages.put("el", PluralMode.MODE_NOTONE);
144 languages.put("en_GB", PluralMode.MODE_NOTONE);
145 languages.put("es", PluralMode.MODE_NOTONE);
146 languages.put("et", PluralMode.MODE_NOTONE);
147 languages.put("fi", PluralMode.MODE_NOTONE);
148 languages.put("fr", PluralMode.MODE_GREATERONE);
149 languages.put("gl", PluralMode.MODE_NOTONE);
150 languages.put("is", PluralMode.MODE_NOTONE);
151 languages.put("it", PluralMode.MODE_NOTONE);
152 languages.put("iw_IL", PluralMode.MODE_NOTONE);
153 languages.put("ja", PluralMode.MODE_NONE);
154 languages.put("nb", PluralMode.MODE_NOTONE);
155 languages.put("nl", PluralMode.MODE_NOTONE);
156 languages.put("pl", PluralMode.MODE_PL);
157 languages.put("pt_BR", PluralMode.MODE_GREATERONE);
158 languages.put("ro", PluralMode.MODE_RO);
159 languages.put("ru", PluralMode.MODE_RU);
160 languages.put("sk", PluralMode.MODE_SK);
161 languages.put("sl", PluralMode.MODE_SL);
162 languages.put("sv", PluralMode.MODE_NOTONE);
163 languages.put("tr", PluralMode.MODE_NONE);
164 languages.put("zh_TW", PluralMode.MODE_NONE);
165
166 /* try initial language settings, may be changed later again */
167 if(!load(Locale.getDefault().toString())) {
168 Locale.setDefault(Locale.ENGLISH);
169 }
170 }
171
172 private static boolean load(String l)
173 {
174 if(l.equals("en") || l.equals("en_US"))
175 {
176 strings = null;
177 pstrings = null;
178 pluralMode = PluralMode.MODE_NOTONE;
179 return true;
180 }
181 URL en = Main.class.getResource("/data/en.lang");
182 if(en == null)
183 return false;
184 URL tr = Main.class.getResource("/data/"+l+".lang");
185 if(tr == null)
186 {
187 int i = l.indexOf('_');
188 if (i > 0) {
189 l = l.substring(0, i);
190 }
191 tr = Main.class.getResource("/data/"+l+".lang");
192 if(tr == null)
193 return false;
194 }
195
196 HashMap<String, String> s = new HashMap<String, String>();
197 HashMap<String, String[]> p = new HashMap<String, String[]>();
198 /* file format:
199 for all single strings:
200 {
201 unsigned short (2 byte) stringlength
202 string
203 }
204 unsigned short (2 byte) 0xFFFF (marks end of single strings)
205 for all multi strings:
206 {
207 unsigned char (1 byte) stringcount
208 for stringcount
209 unsigned short (2 byte) stringlength
210 string
211 }
212 */
213 try
214 {
215 InputStream ens = new BufferedInputStream(en.openStream());
216 InputStream trs = new BufferedInputStream(tr.openStream());
217 byte[] enlen = new byte[2];
218 byte[] trlen = new byte[2];
219 boolean multimode = false;
220 byte[] str = new byte[4096];
221 for(;;)
222 {
223 if(multimode)
224 {
225 int ennum = ens.read();
226 int trnum = trs.read();
227 if((ennum == -1 && trnum != -1) || (ennum != -1 && trnum == -1)) /* files do not match */
228 return false;
229 if(ennum == -1) {
230 break;
231 }
232 String[] enstrings = new String[ennum];
233 String[] trstrings = new String[trnum];
234 for(int i = 0; i < ennum; ++i)
235 {
236 int val = ens.read(enlen);
237 if(val != 2) /* file corrupt */
238 return false;
239 val = (enlen[0] < 0 ? 256+enlen[0]:enlen[0])*256+(enlen[1] < 0 ? 256+enlen[1]:enlen[1]);
240 if(val > str.length) {
241 str = new byte[val];
242 }
243 int rval = ens.read(str, 0, val);
244 if(rval != val) /* file corrupt */
245 return false;
246 enstrings[i] = new String(str, 0, val, "utf-8");
247 }
248 for(int i = 0; i < trnum; ++i)
249 {
250 int val = trs.read(trlen);
251 if(val != 2) /* file corrupt */
252 return false;
253 val = (trlen[0] < 0 ? 256+trlen[0]:trlen[0])*256+(trlen[1] < 0 ? 256+trlen[1]:trlen[1]);
254 if(val > str.length) {
255 str = new byte[val];
256 }
257 int rval = trs.read(str, 0, val);
258 if(rval != val) /* file corrupt */
259 return false;
260 trstrings[i] = new String(str, 0, val, "utf-8");
261 }
262 if(trnum > 0) {
263 p.put(enstrings[0], trstrings);
264 }
265 }
266 else
267 {
268 int enval = ens.read(enlen);
269 int trval = trs.read(trlen);
270 if(enval != trval) /* files do not match */
271 return false;
272 if(enval == -1) {
273 break;
274 }
275 if(enval != 2) /* files corrupt */
276 return false;
277 enval = (enlen[0] < 0 ? 256+enlen[0]:enlen[0])*256+(enlen[1] < 0 ? 256+enlen[1]:enlen[1]);
278 trval = (trlen[0] < 0 ? 256+trlen[0]:trlen[0])*256+(trlen[1] < 0 ? 256+trlen[1]:trlen[1]);
279 if(enval == 0xFFFF)
280 {
281 multimode = true;
282 if(trval != 0xFFFF) /* files do not match */
283 return false;
284 }
285 else
286 {
287 if(enval > str.length) {
288 str = new byte[enval];
289 }
290 if(trval > str.length) {
291 str = new byte[trval];
292 }
293 int val = ens.read(str, 0, enval);
294 if(val != enval) /* file corrupt */
295 return false;
296 String enstr = new String(str, 0, enval, "utf-8");
297 if(trval != 0)
298 {
299 val = trs.read(str, 0, trval);
300 if(val != trval) /* file corrupt */
301 return false;
302 String trstr = new String(str, 0, trval, "utf-8");
303 s.put(enstr, trstr);
304 }
305 }
306 }
307 }
308 }
309 catch(Exception e)
310 {
311 return false;
312 }
313 if(!s.isEmpty() && languages.containsKey(l))
314 {
315 strings = s;
316 pstrings = p;
317 pluralMode = languages.get(l);
318 return true;
319 }
320 return false;
321 }
322
323 /**
324 * Sets the default locale (see {@see Locale#setDefault(Locale)} to the local
325 * given by <code>localName</code>.
326 *
327 * Ignored if localName is null. If the locale with name <code>localName</code>
328 * isn't found the default local is set to <tt>en</tt> (english).
329 *
330 * @param localeName the locale name. Ignored if null.
331 */
332 public static void set(String localeName){
333 if (localeName != null) {
334 Locale l;
335 if (localeName.equals("he")) {
336 localeName = "iw_IL";
337 }
338 int i = localeName.indexOf('_');
339 if (i > 0) {
340 l = new Locale(localeName.substring(0, i), localeName.substring(i + 1));
341 } else {
342 l = new Locale(localeName);
343 }
344 if(load(localeName)) {
345 Locale.setDefault(l);
346 } else {
347 if (!l.getLanguage().equals("en")) {
348 System.out.println(tr("Unable to find translation for the locale {0}. Reverting to {1}.",
349 l.getDisplayName(), Locale.getDefault().getDisplayName()));
350 } else {
351 strings = null;
352 pstrings = null;
353 }
354 }
355 }
356 }
357
358 private static int pluralEval(long n)
359 {
360 switch(pluralMode)
361 {
362 case MODE_NOTONE: /* bg, da, de, el, en, en_GB, es, et, fi, gl, is, it, iw_IL, nb, nl, sv */
363 return ((n != 1) ? 1 : 0);
364 case MODE_NONE: /* ja, tr, zh_TW */
365 return 0;
366 case MODE_GREATERONE: /* fr, pt_BR */
367 return ((n > 1) ? 1 : 0);
368 case MODE_CS:
369 return ((n == 1) ? 0 : (((n >= 2) && (n <= 4)) ? 1 : 2));
370 case MODE_AR:
371 return ((n == 0) ? 0 : ((n == 1) ? 1 : ((n == 2) ? 2 : ((((n % 100) >= 3)
372 && ((n % 100) <= 10)) ? 3 : ((((n % 100) >= 11) && ((n % 100) <= 99)) ? 4 : 5)))));
373 case MODE_PL:
374 return ((n == 1) ? 0 : (((((n % 10) >= 2) && ((n % 10) <= 4))
375 && (((n % 100) < 10) || ((n % 100) >= 20))) ? 1 : 2));
376 case MODE_RO:
377 return ((n == 1) ? 0 : ((((n % 100) > 19) || (((n % 100) == 0) && (n != 0))) ? 2 : 1));
378 case MODE_RU:
379 return ((((n % 10) == 1) && ((n % 100) != 11)) ? 0 : (((((n % 10) >= 2)
380 && ((n % 10) <= 4)) && (((n % 100) < 10) || ((n % 100) >= 20))) ? 1 : 2));
381 case MODE_SK:
382 return ((n == 1) ? 1 : (((n >= 2) && (n <= 4)) ? 2 : 0));
383 case MODE_SL:
384 return (((n % 100) == 1) ? 1 : (((n % 100) == 2) ? 2 : ((((n % 100) == 3)
385 || ((n % 100) == 4)) ? 3 : 0)));
386 }
387 return 0;
388 }
389}
Note: See TracBrowser for help on using the repository browser.