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

Last change on this file since 3779 was 3530, checked in by stoecker, 14 years ago

fix array preferences

  • Property svn:eol-style set to native
File size: 20.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 javax.swing.JColorChooser;
15import javax.swing.JFileChooser;
16import javax.swing.UIManager;
17
18import org.openstreetmap.josm.Main;
19
20/**
21 * Internationalisation support.
22 *
23 * @author Immanuel.Scholz
24 */
25public class I18n {
26 private enum PluralMode { MODE_NOTONE, MODE_NONE, MODE_GREATERONE,
27 MODE_CS, MODE_AR, MODE_PL, MODE_RO, MODE_RU, MODE_SK, MODE_SL}
28 private static PluralMode pluralMode = PluralMode.MODE_NOTONE; /* english default */
29
30 /* Localization keys for file chooser (and color chooser). */
31 private static final String[] javaInternalMessageKeys = new String[] {
32 /* JFileChooser windows laf */
33 "FileChooser.detailsViewActionLabelText",
34 "FileChooser.detailsViewButtonAccessibleName",
35 "FileChooser.detailsViewButtonToolTipText",
36 "FileChooser.fileAttrHeaderText",
37 "FileChooser.fileDateHeaderText",
38 "FileChooser.fileNameHeaderText",
39 "FileChooser.fileNameLabelText",
40 "FileChooser.fileSizeHeaderText",
41 "FileChooser.fileTypeHeaderText",
42 "FileChooser.filesOfTypeLabelText",
43 "FileChooser.homeFolderAccessibleName",
44 "FileChooser.homeFolderToolTipText",
45 "FileChooser.listViewActionLabelText",
46 "FileChooser.listViewButtonAccessibleName",
47 "FileChooser.listViewButtonToolTipText",
48 "FileChooser.lookInLabelText",
49 "FileChooser.newFolderAccessibleName",
50 "FileChooser.newFolderActionLabelText",
51 "FileChooser.newFolderToolTipText",
52 "FileChooser.refreshActionLabelText",
53 "FileChooser.saveInLabelText",
54 "FileChooser.upFolderAccessibleName",
55 "FileChooser.upFolderToolTipText",
56 "FileChooser.viewMenuLabelText",
57
58 /* JFileChooser gtk laf */
59 "FileChooser.acceptAllFileFilterText",
60 "FileChooser.cancelButtonText",
61 "FileChooser.cancelButtonToolTipText",
62 "FileChooser.deleteFileButtonText",
63 "FileChooser.filesLabelText",
64 "FileChooser.filterLabelText",
65 "FileChooser.foldersLabelText",
66 "FileChooser.newFolderButtonText",
67 "FileChooser.newFolderDialogText",
68 "FileChooser.openButtonText",
69 "FileChooser.openButtonToolTipText",
70 "FileChooser.openDialogTitleText",
71 "FileChooser.pathLabelText",
72 "FileChooser.renameFileButtonText",
73 "FileChooser.renameFileDialogText",
74 "FileChooser.renameFileErrorText",
75 "FileChooser.renameFileErrorTitle",
76 "FileChooser.saveButtonText",
77 "FileChooser.saveButtonToolTipText",
78 "FileChooser.saveDialogTitleText",
79
80 /* JFileChooser motif laf */
81 //"FileChooser.cancelButtonText",
82 //"FileChooser.cancelButtonToolTipText",
83 "FileChooser.enterFileNameLabelText",
84 //"FileChooser.filesLabelText",
85 //"FileChooser.filterLabelText",
86 //"FileChooser.foldersLabelText",
87 "FileChooser.helpButtonText",
88 "FileChooser.helpButtonToolTipText",
89 //"FileChooser.openButtonText",
90 //"FileChooser.openButtonToolTipText",
91 //"FileChooser.openDialogTitleText",
92 //"FileChooser.pathLabelText",
93 //"FileChooser.saveButtonText",
94 //"FileChooser.saveButtonToolTipText",
95 //"FileChooser.saveDialogTitleText",
96 "FileChooser.updateButtonText",
97 "FileChooser.updateButtonToolTipText",
98
99 /* gtk color chooser */
100 "GTKColorChooserPanel.blueText",
101 "GTKColorChooserPanel.colorNameText",
102 "GTKColorChooserPanel.greenText",
103 "GTKColorChooserPanel.hueText",
104 "GTKColorChooserPanel.nameText",
105 "GTKColorChooserPanel.redText",
106 "GTKColorChooserPanel.saturationText",
107 "GTKColorChooserPanel.valueText",
108
109 /* JOptionPane */
110 "OptionPane.okButtonText",
111 "OptionPane.yesButtonText",
112 "OptionPane.noButtonText",
113 "OptionPane.cancelButtonText"
114 };
115 private static HashMap<String, String> strings = null;
116 private static HashMap<String, String[]> pstrings = null;
117 private static HashMap<String, PluralMode> languages = new HashMap<String, PluralMode>();
118
119 /**
120 * Translates some text for the current locale.
121 * These strings are collected by a script that runs on the source code files.
122 * After translation, the localizations are distributed with the main program.
123 *
124 * @param text the text to translate.
125 * Must be a string literal. (No constants or local vars.)
126 * Can be broken over multiple lines.
127 * An apostrophe ' must be quoted by another apostrophe.
128 * @param objects Parameters for the string.
129 * Mark occurrences in <code>text</code> with {0}, {1}, ...
130 * @return the translated string
131 *
132 * Example: tr("JOSM''s default value is ''{0}''.", val);
133 */
134 public static final String tr(String text, Object... objects) {
135 return MessageFormat.format(gettext(text, null), objects);
136 }
137
138 public static final String tr(String text) {
139 if (text == null)
140 return null;
141 return MessageFormat.format(gettext(text, null), (Object)null);
142 }
143
144 /**
145 * Provide translation in a context.
146 * There can be different translations for the same text (but
147 * different context).
148 *
149 * @param context string that helps translators to find an appropriate
150 * translation for <code>text</code>
151 * @param text the text to translate
152 * @return the translated string
153 */
154 public static final String trc(String context, String text) {
155 if (context == null)
156 return tr(text);
157 if (text == null)
158 return null;
159 return MessageFormat.format(gettext(text, context), (Object)null);
160 }
161
162 /**
163 * Marks a string for translation (such that a script can harvest
164 * the translatable strings from the source files).
165 *
166 * Example:
167 * String[] options = new String[] {marktr("up"), marktr("down")};
168 * lbl.setText(tr(options[0]));
169 */
170 public static final String marktr(String text) {
171 return text;
172 }
173
174 public static final String marktrc(String context, String text) {
175 return text;
176 }
177
178 /**
179 * Example: trn("Found {0} error!", "Found {0} errors!", i, Integer.toString(i));
180 */
181 public static final String trn(String text, String pluralText, long n, Object... objects) {
182 return MessageFormat.format(gettextn(text, pluralText, null, n), objects);
183 }
184
185 /**
186 * Example: trn("There was an error!", "There were errors!", i);
187 */
188 public static final String trn(String text, String pluralText, long n) {
189 return MessageFormat.format(gettextn(text, pluralText, null, n), (Object)null);
190 }
191
192 public static final String trnc(String ctx, String text, String pluralText, long n, Object... objects) {
193 return MessageFormat.format(gettextn(text, pluralText, ctx, n), objects);
194 }
195
196 public static final String trnc(String ctx, String text, String pluralText, long n) {
197 return MessageFormat.format(gettextn(text, pluralText, ctx, n), (Object)null);
198 }
199
200 private static final String gettext(String text, String ctx)
201 {
202 int i;
203 if(ctx == null && text.startsWith("_:") && (i = text.indexOf("\n")) >= 0)
204 {
205 ctx = text.substring(2,i-1);
206 text = text.substring(i+1);
207 }
208 if(strings != null)
209 {
210 String trans = strings.get(ctx == null ? text : "_:"+ctx+"\n"+text);
211 if(trans != null)
212 return trans;
213 }
214 if(pstrings != null) {
215 String[] trans = pstrings.get(ctx == null ? text : "_:"+ctx+"\n"+text);
216 if(trans != null)
217 return trans[0];
218 }
219 return text;
220 }
221
222 private static final String gettextn(String text, String plural, String ctx, long num)
223 {
224 int i;
225 if(ctx == null && text.startsWith("_:") && (i = text.indexOf("\n")) >= 0)
226 {
227 ctx = text.substring(2,i-1);
228 text = text.substring(i+1);
229 }
230 if(pstrings != null)
231 {
232 i = pluralEval(num);
233 String[] trans = pstrings.get(ctx == null ? text : "_:"+ctx+"\n"+text);
234 if(trans != null && trans.length > i)
235 return trans[i];
236 }
237
238 return num == 1 ? text : plural;
239 }
240
241 /**
242 * Get a list of all available JOSM Translations.
243 * @return an array of locale objects.
244 */
245 public static final Locale[] getAvailableTranslations() {
246 Vector<Locale> v = new Vector<Locale>();
247 if(Main.class.getResource("/data/en.lang") != null)
248 {
249 for (String loc : languages.keySet()) {
250 if(Main.class.getResource("/data/"+loc+".lang") != null) {
251 int i = loc.indexOf('_');
252 if (i > 0) {
253 v.add(new Locale(loc.substring(0, i), loc.substring(i + 1)));
254 } else {
255 v.add(new Locale(loc));
256 }
257 }
258 }
259 }
260 v.add(Locale.ENGLISH);
261 Locale[] l = new Locale[v.size()];
262 l = v.toArray(l);
263 Arrays.sort(l, new Comparator<Locale>() {
264 public int compare(Locale o1, Locale o2) {
265 return o1.toString().compareTo(o2.toString());
266 }
267 });
268 return l;
269 }
270
271 public static void init()
272 {
273 languages.put("ar", PluralMode.MODE_AR);
274 languages.put("bg", PluralMode.MODE_NOTONE);
275 languages.put("cs", PluralMode.MODE_CS);
276 languages.put("da", PluralMode.MODE_NOTONE);
277 languages.put("de", PluralMode.MODE_NOTONE);
278 languages.put("el", PluralMode.MODE_NOTONE);
279 languages.put("en_AU", PluralMode.MODE_NOTONE);
280 languages.put("en_GB", PluralMode.MODE_NOTONE);
281 languages.put("es", PluralMode.MODE_NOTONE);
282 languages.put("et", PluralMode.MODE_NOTONE);
283 languages.put("eu", PluralMode.MODE_NOTONE);
284 languages.put("fi", PluralMode.MODE_NOTONE);
285 languages.put("fr", PluralMode.MODE_GREATERONE);
286 languages.put("gl", PluralMode.MODE_NOTONE);
287 languages.put("is", PluralMode.MODE_NOTONE);
288 languages.put("it", PluralMode.MODE_NOTONE);
289 languages.put("iw_IL", PluralMode.MODE_NOTONE);
290 languages.put("ja", PluralMode.MODE_NONE);
291 languages.put("nb", PluralMode.MODE_NOTONE);
292 languages.put("nl", PluralMode.MODE_NOTONE);
293 languages.put("pl", PluralMode.MODE_PL);
294 languages.put("pt_BR", PluralMode.MODE_GREATERONE);
295 languages.put("ro", PluralMode.MODE_RO);
296 languages.put("ru", PluralMode.MODE_RU);
297 languages.put("sk", PluralMode.MODE_SK);
298 languages.put("sl", PluralMode.MODE_SL);
299 languages.put("sv", PluralMode.MODE_NOTONE);
300 languages.put("tr", PluralMode.MODE_NONE);
301 languages.put("uk", PluralMode.MODE_RU);
302 languages.put("zh_CN", PluralMode.MODE_NONE);
303 languages.put("zh_TW", PluralMode.MODE_NONE);
304
305 /* try initial language settings, may be changed later again */
306 if(!load(Locale.getDefault().toString())) {
307 Locale.setDefault(Locale.ENGLISH);
308 }
309 }
310
311 private static boolean load(String l)
312 {
313 if(l.equals("en") || l.equals("en_US"))
314 {
315 strings = null;
316 pstrings = null;
317 pluralMode = PluralMode.MODE_NOTONE;
318 return true;
319 }
320 URL en = Main.class.getResource("/data/en.lang");
321 if(en == null)
322 return false;
323 URL tr = Main.class.getResource("/data/"+l+".lang");
324 if(tr == null)
325 {
326 int i = l.indexOf('_');
327 if (i > 0) {
328 l = l.substring(0, i);
329 }
330 tr = Main.class.getResource("/data/"+l+".lang");
331 if(tr == null)
332 return false;
333 }
334
335 HashMap<String, String> s = new HashMap<String, String>();
336 HashMap<String, String[]> p = new HashMap<String, String[]>();
337 /* file format:
338 for all single strings:
339 {
340 unsigned short (2 byte) stringlength
341 string
342 }
343 unsigned short (2 byte) 0xFFFF (marks end of single strings)
344 for all multi strings:
345 {
346 unsigned char (1 byte) stringcount
347 for stringcount
348 unsigned short (2 byte) stringlength
349 string
350 }
351 */
352 try
353 {
354 InputStream ens = new BufferedInputStream(en.openStream());
355 InputStream trs = new BufferedInputStream(tr.openStream());
356 byte[] enlen = new byte[2];
357 byte[] trlen = new byte[2];
358 boolean multimode = false;
359 byte[] str = new byte[4096];
360 for(;;)
361 {
362 if(multimode)
363 {
364 int ennum = ens.read();
365 int trnum = trs.read();
366 if((ennum == -1 && trnum != -1) || (ennum != -1 && trnum == -1)) /* files do not match */
367 return false;
368 if(ennum == -1) {
369 break;
370 }
371 String[] enstrings = new String[ennum];
372 String[] trstrings = new String[trnum];
373 for(int i = 0; i < ennum; ++i)
374 {
375 int val = ens.read(enlen);
376 if(val != 2) /* file corrupt */
377 return false;
378 val = (enlen[0] < 0 ? 256+enlen[0]:enlen[0])*256+(enlen[1] < 0 ? 256+enlen[1]:enlen[1]);
379 if(val > str.length) {
380 str = new byte[val];
381 }
382 int rval = ens.read(str, 0, val);
383 if(rval != val) /* file corrupt */
384 return false;
385 enstrings[i] = new String(str, 0, val, "utf-8");
386 }
387 for(int i = 0; i < trnum; ++i)
388 {
389 int val = trs.read(trlen);
390 if(val != 2) /* file corrupt */
391 return false;
392 val = (trlen[0] < 0 ? 256+trlen[0]:trlen[0])*256+(trlen[1] < 0 ? 256+trlen[1]:trlen[1]);
393 if(val > str.length) {
394 str = new byte[val];
395 }
396 int rval = trs.read(str, 0, val);
397 if(rval != val) /* file corrupt */
398 return false;
399 trstrings[i] = new String(str, 0, val, "utf-8");
400 }
401 if(trnum > 0) {
402 p.put(enstrings[0], trstrings);
403 }
404 }
405 else
406 {
407 int enval = ens.read(enlen);
408 int trval = trs.read(trlen);
409 if(enval != trval) /* files do not match */
410 return false;
411 if(enval == -1) {
412 break;
413 }
414 if(enval != 2) /* files corrupt */
415 return false;
416 enval = (enlen[0] < 0 ? 256+enlen[0]:enlen[0])*256+(enlen[1] < 0 ? 256+enlen[1]:enlen[1]);
417 trval = (trlen[0] < 0 ? 256+trlen[0]:trlen[0])*256+(trlen[1] < 0 ? 256+trlen[1]:trlen[1]);
418 if(enval == 0xFFFF)
419 {
420 multimode = true;
421 if(trval != 0xFFFF) /* files do not match */
422 return false;
423 }
424 else
425 {
426 if(enval > str.length) {
427 str = new byte[enval];
428 }
429 if(trval > str.length) {
430 str = new byte[trval];
431 }
432 int val = ens.read(str, 0, enval);
433 if(val != enval) /* file corrupt */
434 return false;
435 String enstr = new String(str, 0, enval, "utf-8");
436 if(trval != 0)
437 {
438 val = trs.read(str, 0, trval);
439 if(val != trval) /* file corrupt */
440 return false;
441 String trstr = new String(str, 0, trval, "utf-8");
442 s.put(enstr, trstr);
443 }
444 }
445 }
446 }
447 }
448 catch(Exception e)
449 {
450 return false;
451 }
452 if(!s.isEmpty() && languages.containsKey(l))
453 {
454 strings = s;
455 pstrings = p;
456 pluralMode = languages.get(l);
457 return true;
458 }
459 return false;
460 }
461
462 /**
463 * Sets the default locale (see {@see Locale#setDefault(Locale)} to the local
464 * given by <code>localName</code>.
465 *
466 * Ignored if localeName is null. If the locale with name <code>localName</code>
467 * isn't found the default local is set to <tt>en</tt> (english).
468 *
469 * @param localeName the locale name. Ignored if null.
470 */
471 public static void set(String localeName){
472 if (localeName != null) {
473 Locale l;
474 if (localeName.equals("he")) {
475 localeName = "iw_IL";
476 }
477 int i = localeName.indexOf('_');
478 if (i > 0) {
479 l = new Locale(localeName.substring(0, i), localeName.substring(i + 1));
480 } else {
481 l = new Locale(localeName);
482 }
483 if (load(localeName)) {
484 Locale.setDefault(l);
485 } else {
486 if (!l.getLanguage().equals("en")) {
487 System.out.println(tr("Unable to find translation for the locale {0}. Reverting to {1}.",
488 l.getDisplayName(), Locale.getDefault().getDisplayName()));
489 } else {
490 strings = null;
491 pstrings = null;
492 }
493 }
494 }
495 }
496
497 /**
498 * Localizations for file chooser dialog.
499 * For some locales (e.g. de, fr) translations are provided
500 * by Java, but not for others (e.g. ru, uk).
501 */
502 public static void translateJavaInternalMessages() {
503 Locale l = Locale.getDefault();
504
505 JFileChooser.setDefaultLocale(l);
506 JColorChooser.setDefaultLocale(l);
507 for (String key : javaInternalMessageKeys) {
508 String us = UIManager.getString(key, Locale.US);
509 String loc = UIManager.getString(key, l);
510 // only provide custom translation if it is not already localized by Java
511 if (us != null && us.equals(loc)) {
512 UIManager.put(key, tr(us));
513 }
514 }
515 }
516
517 private static int pluralEval(long n)
518 {
519 switch(pluralMode)
520 {
521 case MODE_NOTONE: /* bg, da, de, el, en, en_GB, es, et, eu, fi, gl, is, it, iw_IL, nb, nl, sv */
522 return ((n != 1) ? 1 : 0);
523 case MODE_NONE: /* ja, tr, zh_CN, zh_TW */
524 return 0;
525 case MODE_GREATERONE: /* fr, pt_BR */
526 return ((n > 1) ? 1 : 0);
527 case MODE_CS:
528 return ((n == 1) ? 0 : (((n >= 2) && (n <= 4)) ? 1 : 2));
529 case MODE_AR:
530 return ((n == 0) ? 0 : ((n == 1) ? 1 : ((n == 2) ? 2 : ((((n % 100) >= 3)
531 && ((n % 100) <= 10)) ? 3 : ((((n % 100) >= 11) && ((n % 100) <= 99)) ? 4 : 5)))));
532 case MODE_PL:
533 return ((n == 1) ? 0 : (((((n % 10) >= 2) && ((n % 10) <= 4))
534 && (((n % 100) < 10) || ((n % 100) >= 20))) ? 1 : 2));
535 case MODE_RO:
536 return ((n == 1) ? 0 : ((((n % 100) > 19) || (((n % 100) == 0) && (n != 0))) ? 2 : 1));
537 case MODE_RU:
538 return ((((n % 10) == 1) && ((n % 100) != 11)) ? 0 : (((((n % 10) >= 2)
539 && ((n % 10) <= 4)) && (((n % 100) < 10) || ((n % 100) >= 20))) ? 1 : 2));
540 case MODE_SK:
541 return ((n == 1) ? 1 : (((n >= 2) && (n <= 4)) ? 2 : 0));
542 case MODE_SL:
543 return (((n % 100) == 1) ? 1 : (((n % 100) == 2) ? 2 : ((((n % 100) == 3)
544 || ((n % 100) == 4)) ? 3 : 0)));
545 }
546 return 0;
547 }
548}
Note: See TracBrowser for help on using the repository browser.