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

Last change on this file since 12013 was 10983, checked in by Don-vip, 8 years ago

fix #13609 - --version and --help are too verbose

  • Property svn:eol-style set to native
File size: 30.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import java.awt.GraphicsEnvironment;
5import java.io.BufferedInputStream;
6import java.io.File;
7import java.io.FileInputStream;
8import java.io.IOException;
9import java.io.InputStream;
10import java.lang.annotation.Retention;
11import java.lang.annotation.RetentionPolicy;
12import java.net.URL;
13import java.nio.charset.StandardCharsets;
14import java.text.MessageFormat;
15import java.util.ArrayList;
16import java.util.Arrays;
17import java.util.Collection;
18import java.util.Comparator;
19import java.util.HashMap;
20import java.util.Locale;
21import java.util.Map;
22import java.util.jar.JarInputStream;
23import java.util.zip.ZipEntry;
24
25import javax.swing.JColorChooser;
26import javax.swing.JFileChooser;
27import javax.swing.UIManager;
28
29import org.openstreetmap.gui.jmapviewer.FeatureAdapter.TranslationAdapter;
30import org.openstreetmap.josm.Main;
31import org.openstreetmap.josm.gui.util.GuiHelper;
32import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
33
34/**
35 * Internationalisation support.
36 *
37 * @author Immanuel.Scholz
38 */
39public final class I18n {
40
41 /**
42 * This annotates strings which do not permit a clean i18n. This is mostly due to strings
43 * containing two nouns which can occur in singular or plural form.
44 * <br>
45 * No behaviour is associated with this annotation.
46 */
47 @Retention(RetentionPolicy.SOURCE)
48 public @interface QuirkyPluralString {
49 }
50
51 private I18n() {
52 // Hide default constructor for utils classes
53 }
54
55 /**
56 * Enumeration of possible plural modes. It allows us to identify and implement logical conditions of
57 * plural forms defined on <a href="https://help.launchpad.net/Translations/PluralForms">Launchpad</a>.
58 * See <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html">CLDR</a>
59 * for another complete list.
60 * @see #pluralEval
61 */
62 private enum PluralMode {
63 /** Plural = Not 1. This is the default for many languages, including English: 1 day, but 0 days or 2 days. */
64 MODE_NOTONE,
65 /** No plural. Mainly for Asian languages (Indonesian, Chinese, Japanese, ...) */
66 MODE_NONE,
67 /** Plural = Greater than 1. For some latin languages (French, Brazilian Portuguese) */
68 MODE_GREATERONE,
69 /* Special mode for
70 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ar">Arabic</a>.*
71 MODE_AR,*/
72 /** Special mode for
73 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#cs">Czech</a>. */
74 MODE_CS,
75 /** Special mode for
76 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#pl">Polish</a>. */
77 MODE_PL,
78 /* Special mode for
79 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ro">Romanian</a>.*
80 MODE_RO,*/
81 /** Special mode for
82 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#lt">Lithuanian</a>. */
83 MODE_LT,
84 /** Special mode for
85 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ru">Russian</a>. */
86 MODE_RU,
87 /** Special mode for
88 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#sk">Slovak</a>. */
89 MODE_SK,
90 /* Special mode for
91 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#sl">Slovenian</a>.*
92 MODE_SL,*/
93 }
94
95 private static volatile PluralMode pluralMode = PluralMode.MODE_NOTONE; /* english default */
96 private static volatile String loadedCode = "en";
97
98 /* Localization keys for file chooser (and color chooser). */
99 private static final String[] javaInternalMessageKeys = new String[] {
100 /* JFileChooser windows laf */
101 "FileChooser.detailsViewActionLabelText",
102 "FileChooser.detailsViewButtonAccessibleName",
103 "FileChooser.detailsViewButtonToolTipText",
104 "FileChooser.fileAttrHeaderText",
105 "FileChooser.fileDateHeaderText",
106 "FileChooser.fileNameHeaderText",
107 "FileChooser.fileNameLabelText",
108 "FileChooser.fileSizeHeaderText",
109 "FileChooser.fileTypeHeaderText",
110 "FileChooser.filesOfTypeLabelText",
111 "FileChooser.homeFolderAccessibleName",
112 "FileChooser.homeFolderToolTipText",
113 "FileChooser.listViewActionLabelText",
114 "FileChooser.listViewButtonAccessibleName",
115 "FileChooser.listViewButtonToolTipText",
116 "FileChooser.lookInLabelText",
117 "FileChooser.newFolderAccessibleName",
118 "FileChooser.newFolderActionLabelText",
119 "FileChooser.newFolderToolTipText",
120 "FileChooser.refreshActionLabelText",
121 "FileChooser.saveInLabelText",
122 "FileChooser.upFolderAccessibleName",
123 "FileChooser.upFolderToolTipText",
124 "FileChooser.viewMenuLabelText",
125
126 /* JFileChooser gtk laf */
127 "FileChooser.acceptAllFileFilterText",
128 "FileChooser.cancelButtonText",
129 "FileChooser.cancelButtonToolTipText",
130 "FileChooser.deleteFileButtonText",
131 "FileChooser.filesLabelText",
132 "FileChooser.filterLabelText",
133 "FileChooser.foldersLabelText",
134 "FileChooser.newFolderButtonText",
135 "FileChooser.newFolderDialogText",
136 "FileChooser.openButtonText",
137 "FileChooser.openButtonToolTipText",
138 "FileChooser.openDialogTitleText",
139 "FileChooser.pathLabelText",
140 "FileChooser.renameFileButtonText",
141 "FileChooser.renameFileDialogText",
142 "FileChooser.renameFileErrorText",
143 "FileChooser.renameFileErrorTitle",
144 "FileChooser.saveButtonText",
145 "FileChooser.saveButtonToolTipText",
146 "FileChooser.saveDialogTitleText",
147
148 /* JFileChooser motif laf */
149 //"FileChooser.cancelButtonText",
150 //"FileChooser.cancelButtonToolTipText",
151 "FileChooser.enterFileNameLabelText",
152 //"FileChooser.filesLabelText",
153 //"FileChooser.filterLabelText",
154 //"FileChooser.foldersLabelText",
155 "FileChooser.helpButtonText",
156 "FileChooser.helpButtonToolTipText",
157 //"FileChooser.openButtonText",
158 //"FileChooser.openButtonToolTipText",
159 //"FileChooser.openDialogTitleText",
160 //"FileChooser.pathLabelText",
161 //"FileChooser.saveButtonText",
162 //"FileChooser.saveButtonToolTipText",
163 //"FileChooser.saveDialogTitleText",
164 "FileChooser.updateButtonText",
165 "FileChooser.updateButtonToolTipText",
166
167 /* gtk color chooser */
168 "GTKColorChooserPanel.blueText",
169 "GTKColorChooserPanel.colorNameText",
170 "GTKColorChooserPanel.greenText",
171 "GTKColorChooserPanel.hueText",
172 "GTKColorChooserPanel.nameText",
173 "GTKColorChooserPanel.redText",
174 "GTKColorChooserPanel.saturationText",
175 "GTKColorChooserPanel.valueText",
176
177 /* JOptionPane */
178 "OptionPane.okButtonText",
179 "OptionPane.yesButtonText",
180 "OptionPane.noButtonText",
181 "OptionPane.cancelButtonText"
182 };
183 private static volatile Map<String, String> strings;
184 private static volatile Map<String, String[]> pstrings;
185 private static Map<String, PluralMode> languages = new HashMap<>();
186
187 /**
188 * Translates some text for the current locale.
189 * These strings are collected by a script that runs on the source code files.
190 * After translation, the localizations are distributed with the main program.
191 * <br>
192 * For example, <code>tr("JOSM''s default value is ''{0}''.", val)</code>.
193 * <br>
194 * Use {@link #trn} for distinguishing singular from plural text, i.e.,
195 * do not use {@code tr(size == 1 ? "singular" : "plural")} nor
196 * {@code size == 1 ? tr("singular") : tr("plural")}
197 *
198 * @param text the text to translate.
199 * Must be a string literal. (No constants or local vars.)
200 * Can be broken over multiple lines.
201 * An apostrophe ' must be quoted by another apostrophe.
202 * @param objects the parameters for the string.
203 * Mark occurrences in {@code text} with <code>{0}</code>, <code>{1}</code>, ...
204 * @return the translated string.
205 * @see #trn
206 * @see #trc
207 * @see #trnc
208 */
209 public static String tr(String text, Object... objects) {
210 if (text == null) return null;
211 return MessageFormat.format(gettext(text, null), objects);
212 }
213
214 /**
215 * Translates some text in a context for the current locale.
216 * There can be different translations for the same text within different contexts.
217 *
218 * @param context string that helps translators to find an appropriate
219 * translation for {@code text}.
220 * @param text the text to translate.
221 * @return the translated string.
222 * @see #tr
223 * @see #trn
224 * @see #trnc
225 */
226 public static String trc(String context, String text) {
227 if (context == null)
228 return tr(text);
229 if (text == null)
230 return null;
231 return MessageFormat.format(gettext(text, context), (Object) null);
232 }
233
234 public static String trcLazy(String context, String text) {
235 if (context == null)
236 return tr(text);
237 if (text == null)
238 return null;
239 return MessageFormat.format(gettextLazy(text, context), (Object) null);
240 }
241
242 /**
243 * Marks a string for translation (such that a script can harvest
244 * the translatable strings from the source files).
245 *
246 * For example, <code>
247 * String[] options = new String[] {marktr("up"), marktr("down")};
248 * lbl.setText(tr(options[0]));</code>
249 * @param text the string to be marked for translation.
250 * @return {@code text} unmodified.
251 */
252 public static String marktr(String text) {
253 return text;
254 }
255
256 public static String marktrc(String context, String text) {
257 return text;
258 }
259
260 /**
261 * Translates some text for the current locale and distinguishes between
262 * {@code singularText} and {@code pluralText} depending on {@code n}.
263 * <br>
264 * For instance, {@code trn("There was an error!", "There were errors!", i)} or
265 * <code>trn("Found {0} error in {1}!", "Found {0} errors in {1}!", i, Integer.toString(i), url)</code>.
266 *
267 * @param singularText the singular text to translate.
268 * Must be a string literal. (No constants or local vars.)
269 * Can be broken over multiple lines.
270 * An apostrophe ' must be quoted by another apostrophe.
271 * @param pluralText the plural text to translate.
272 * Must be a string literal. (No constants or local vars.)
273 * Can be broken over multiple lines.
274 * An apostrophe ' must be quoted by another apostrophe.
275 * @param n a number to determine whether {@code singularText} or {@code pluralText} is used.
276 * @param objects the parameters for the string.
277 * Mark occurrences in {@code singularText} and {@code pluralText} with <code>{0}</code>, <code>{1}</code>, ...
278 * @return the translated string.
279 * @see #tr
280 * @see #trc
281 * @see #trnc
282 */
283 public static String trn(String singularText, String pluralText, long n, Object... objects) {
284 return MessageFormat.format(gettextn(singularText, pluralText, null, n), objects);
285 }
286
287 /**
288 * Translates some text in a context for the current locale and distinguishes between
289 * {@code singularText} and {@code pluralText} depending on {@code n}.
290 * There can be different translations for the same text within different contexts.
291 *
292 * @param context string that helps translators to find an appropriate
293 * translation for {@code text}.
294 * @param singularText the singular text to translate.
295 * Must be a string literal. (No constants or local vars.)
296 * Can be broken over multiple lines.
297 * An apostrophe ' must be quoted by another apostrophe.
298 * @param pluralText the plural text to translate.
299 * Must be a string literal. (No constants or local vars.)
300 * Can be broken over multiple lines.
301 * An apostrophe ' must be quoted by another apostrophe.
302 * @param n a number to determine whether {@code singularText} or {@code pluralText} is used.
303 * @param objects the parameters for the string.
304 * Mark occurrences in {@code singularText} and {@code pluralText} with <code>{0}</code>, <code>{1}</code>, ...
305 * @return the translated string.
306 * @see #tr
307 * @see #trc
308 * @see #trn
309 */
310 public static String trnc(String context, String singularText, String pluralText, long n, Object... objects) {
311 return MessageFormat.format(gettextn(singularText, pluralText, context, n), objects);
312 }
313
314 private static String gettext(String text, String ctx, boolean lazy) {
315 int i;
316 if (ctx == null && text.startsWith("_:") && (i = text.indexOf('\n')) >= 0) {
317 ctx = text.substring(2, i-1);
318 text = text.substring(i+1);
319 }
320 if (strings != null) {
321 String trans = strings.get(ctx == null ? text : "_:"+ctx+'\n'+text);
322 if (trans != null)
323 return trans;
324 }
325 if (pstrings != null) {
326 i = pluralEval(1);
327 String[] trans = pstrings.get(ctx == null ? text : "_:"+ctx+'\n'+text);
328 if (trans != null && trans.length > i)
329 return trans[i];
330 }
331 return lazy ? gettext(text, null) : text;
332 }
333
334 private static String gettext(String text, String ctx) {
335 return gettext(text, ctx, false);
336 }
337
338 /* try without context, when context try fails */
339 private static String gettextLazy(String text, String ctx) {
340 return gettext(text, ctx, true);
341 }
342
343 private static String gettextn(String text, String plural, String ctx, long num) {
344 int i;
345 if (ctx == null && text.startsWith("_:") && (i = text.indexOf('\n')) >= 0) {
346 ctx = text.substring(2, i-1);
347 text = text.substring(i+1);
348 }
349 if (pstrings != null) {
350 i = pluralEval(num);
351 String[] trans = pstrings.get(ctx == null ? text : "_:"+ctx+'\n'+text);
352 if (trans != null && trans.length > i)
353 return trans[i];
354 }
355
356 return num == 1 ? text : plural;
357 }
358
359 public static String escape(String msg) {
360 if (msg == null) return null;
361 return msg.replace("\'", "\'\'").replace("{", "\'{\'").replace("}", "\'}\'");
362 }
363
364 private static URL getTranslationFile(String lang) {
365 return Main.class.getResource("/data/"+lang.replace('@', '-')+".lang");
366 }
367
368 /**
369 * Get a list of all available JOSM Translations.
370 * @return an array of locale objects.
371 */
372 public static Locale[] getAvailableTranslations() {
373 Collection<Locale> v = new ArrayList<>(languages.size());
374 if (getTranslationFile("en") != null) {
375 for (String loc : languages.keySet()) {
376 if (getTranslationFile(loc) != null) {
377 v.add(LanguageInfo.getLocale(loc));
378 }
379 }
380 }
381 v.add(Locale.ENGLISH);
382 Locale[] l = new Locale[v.size()];
383 l = v.toArray(l);
384 Arrays.sort(l, Comparator.comparing(Locale::toString));
385 return l;
386 }
387
388 /**
389 * Determines if a language exists for the given code.
390 * @param code The language code
391 * @return {@code true} if a language exists, {@code false} otherwise
392 */
393 public static boolean hasCode(String code) {
394 return languages.containsKey(code);
395 }
396
397 /**
398 * I18n initialization.
399 */
400 public static void init() {
401 // Enable CLDR locale provider on Java 8 to get additional languages, such as Khmer.
402 // http://docs.oracle.com/javase/8/docs/technotes/guides/intl/enhancements.8.html#cldr
403 // FIXME: This can be removed after we switch to a minimal version of Java that enables CLDR by default
404 // or includes all languages we need in the JRE. See http://openjdk.java.net/jeps/252 for Java 9
405 System.setProperty("java.locale.providers", "JRE,CLDR"); // Don't call Utils.updateSystemProperty to avoid spurious log at startup
406
407 //languages.put("ar", PluralMode.MODE_AR);
408 languages.put("ast", PluralMode.MODE_NOTONE);
409 languages.put("bg", PluralMode.MODE_NOTONE);
410 languages.put("be", PluralMode.MODE_RU);
411 languages.put("ca", PluralMode.MODE_NOTONE);
412 languages.put("ca@valencia", PluralMode.MODE_NOTONE);
413 languages.put("cs", PluralMode.MODE_CS);
414 languages.put("da", PluralMode.MODE_NOTONE);
415 languages.put("de", PluralMode.MODE_NOTONE);
416 languages.put("el", PluralMode.MODE_NOTONE);
417 languages.put("en_AU", PluralMode.MODE_NOTONE);
418 languages.put("en_GB", PluralMode.MODE_NOTONE);
419 languages.put("es", PluralMode.MODE_NOTONE);
420 languages.put("et", PluralMode.MODE_NOTONE);
421 //languages.put("eu", PluralMode.MODE_NOTONE);
422 languages.put("fi", PluralMode.MODE_NOTONE);
423 languages.put("fr", PluralMode.MODE_GREATERONE);
424 languages.put("gl", PluralMode.MODE_NOTONE);
425 //languages.put("he", PluralMode.MODE_NOTONE);
426 languages.put("hu", PluralMode.MODE_NOTONE);
427 languages.put("id", PluralMode.MODE_NONE);
428 //languages.put("is", PluralMode.MODE_NOTONE);
429 languages.put("it", PluralMode.MODE_NOTONE);
430 languages.put("ja", PluralMode.MODE_NONE);
431 // fully supported only with Java 8 and later (needs CLDR)
432 languages.put("km", PluralMode.MODE_NONE);
433 languages.put("lt", PluralMode.MODE_LT);
434 languages.put("nb", PluralMode.MODE_NOTONE);
435 languages.put("nl", PluralMode.MODE_NOTONE);
436 languages.put("pl", PluralMode.MODE_PL);
437 languages.put("pt", PluralMode.MODE_NOTONE);
438 languages.put("pt_BR", PluralMode.MODE_GREATERONE);
439 //languages.put("ro", PluralMode.MODE_RO);
440 languages.put("ru", PluralMode.MODE_RU);
441 languages.put("sk", PluralMode.MODE_SK);
442 //languages.put("sl", PluralMode.MODE_SL);
443 languages.put("sv", PluralMode.MODE_NOTONE);
444 //languages.put("tr", PluralMode.MODE_NONE);
445 languages.put("uk", PluralMode.MODE_RU);
446 languages.put("vi", PluralMode.MODE_NONE);
447 languages.put("zh_CN", PluralMode.MODE_NONE);
448 languages.put("zh_TW", PluralMode.MODE_NONE);
449
450 /* try initial language settings, may be changed later again */
451 if (!load(LanguageInfo.getJOSMLocaleCode())) {
452 Locale.setDefault(Locale.ENGLISH);
453 }
454 }
455
456 public static void addTexts(File source) {
457 if ("en".equals(loadedCode))
458 return;
459 final String enfile = "data/en.lang";
460 final String langfile = "data/"+loadedCode+".lang";
461 try (
462 FileInputStream fis = new FileInputStream(source);
463 JarInputStream jar = new JarInputStream(fis)
464 ) {
465 ZipEntry e;
466 boolean found = false;
467 while (!found && (e = jar.getNextEntry()) != null) {
468 String name = e.getName();
469 if (enfile.equals(name))
470 found = true;
471 }
472 if (found) {
473 try (
474 FileInputStream fisTrans = new FileInputStream(source);
475 JarInputStream jarTrans = new JarInputStream(fisTrans)
476 ) {
477 found = false;
478 while (!found && (e = jarTrans.getNextEntry()) != null) {
479 String name = e.getName();
480 if (name.equals(langfile))
481 found = true;
482 }
483 if (found)
484 load(jar, jarTrans, true);
485 }
486 }
487 } catch (IOException e) {
488 // Ignore
489 Main.trace(e);
490 }
491 }
492
493 private static boolean load(String l) {
494 if ("en".equals(l) || "en_US".equals(l)) {
495 strings = null;
496 pstrings = null;
497 loadedCode = "en";
498 pluralMode = PluralMode.MODE_NOTONE;
499 return true;
500 }
501 URL en = getTranslationFile("en");
502 if (en == null)
503 return false;
504 URL tr = getTranslationFile(l);
505 if (tr == null || !languages.containsKey(l)) {
506 return false;
507 }
508 try (
509 InputStream enStream = en.openStream();
510 InputStream trStream = tr.openStream()
511 ) {
512 if (load(enStream, trStream, false)) {
513 pluralMode = languages.get(l);
514 loadedCode = l;
515 return true;
516 }
517 } catch (IOException e) {
518 // Ignore exception
519 Main.trace(e);
520 }
521 return false;
522 }
523
524 private static boolean load(InputStream en, InputStream tr, boolean add) {
525 Map<String, String> s;
526 Map<String, String[]> p;
527 if (add) {
528 s = strings;
529 p = pstrings;
530 } else {
531 s = new HashMap<>();
532 p = new HashMap<>();
533 }
534 /* file format:
535 Files are always a group. English file and translated file must provide identical datasets.
536
537 for all single strings:
538 {
539 unsigned short (2 byte) stringlength
540 - length 0 indicates missing translation
541 - length 0xFFFE indicates translation equal to original, but otherwise is equal to length 0
542 string
543 }
544 unsigned short (2 byte) 0xFFFF (marks end of single strings)
545 for all multi strings:
546 {
547 unsigned char (1 byte) stringcount
548 - count 0 indicates missing translations
549 - count 0xFE indicates translations equal to original, but otherwise is equal to length 0
550 for stringcount
551 unsigned short (2 byte) stringlength
552 string
553 }
554 */
555 try {
556 InputStream ens = new BufferedInputStream(en);
557 InputStream trs = new BufferedInputStream(tr);
558 byte[] enlen = new byte[2];
559 byte[] trlen = new byte[2];
560 boolean multimode = false;
561 byte[] str = new byte[4096];
562 for (;;) {
563 if (multimode) {
564 int ennum = ens.read();
565 int trnum = trs.read();
566 if (trnum == 0xFE) /* marks identical string, handle equally to non-translated */
567 trnum = 0;
568 if ((ennum == -1 && trnum != -1) || (ennum != -1 && trnum == -1)) /* files do not match */
569 return false;
570 if (ennum == -1) {
571 break;
572 }
573 String[] enstrings = new String[ennum];
574 for (int i = 0; i < ennum; ++i) {
575 int val = ens.read(enlen);
576 if (val != 2) /* file corrupt */
577 return false;
578 val = (enlen[0] < 0 ? 256+enlen[0] : enlen[0])*256+(enlen[1] < 0 ? 256+enlen[1] : enlen[1]);
579 if (val > str.length) {
580 str = new byte[val];
581 }
582 int rval = ens.read(str, 0, val);
583 if (rval != val) /* file corrupt */
584 return false;
585 enstrings[i] = new String(str, 0, val, StandardCharsets.UTF_8);
586 }
587 String[] trstrings = new String[trnum];
588 for (int i = 0; i < trnum; ++i) {
589 int val = trs.read(trlen);
590 if (val != 2) /* file corrupt */
591 return false;
592 val = (trlen[0] < 0 ? 256+trlen[0] : trlen[0])*256+(trlen[1] < 0 ? 256+trlen[1] : trlen[1]);
593 if (val > str.length) {
594 str = new byte[val];
595 }
596 int rval = trs.read(str, 0, val);
597 if (rval != val) /* file corrupt */
598 return false;
599 trstrings[i] = new String(str, 0, val, StandardCharsets.UTF_8);
600 }
601 if (trnum > 0 && !p.containsKey(enstrings[0])) {
602 p.put(enstrings[0], trstrings);
603 }
604 } else {
605 int enval = ens.read(enlen);
606 int trval = trs.read(trlen);
607 if (enval != trval) /* files do not match */
608 return false;
609 if (enval == -1) {
610 break;
611 }
612 if (enval != 2) /* files corrupt */
613 return false;
614 enval = (enlen[0] < 0 ? 256+enlen[0] : enlen[0])*256+(enlen[1] < 0 ? 256+enlen[1] : enlen[1]);
615 trval = (trlen[0] < 0 ? 256+trlen[0] : trlen[0])*256+(trlen[1] < 0 ? 256+trlen[1] : trlen[1]);
616 if (trval == 0xFFFE) /* marks identical string, handle equally to non-translated */
617 trval = 0;
618 if (enval == 0xFFFF) {
619 multimode = true;
620 if (trval != 0xFFFF) /* files do not match */
621 return false;
622 } else {
623 if (enval > str.length) {
624 str = new byte[enval];
625 }
626 if (trval > str.length) {
627 str = new byte[trval];
628 }
629 int val = ens.read(str, 0, enval);
630 if (val != enval) /* file corrupt */
631 return false;
632 String enstr = new String(str, 0, enval, StandardCharsets.UTF_8);
633 if (trval != 0) {
634 val = trs.read(str, 0, trval);
635 if (val != trval) /* file corrupt */
636 return false;
637 String trstr = new String(str, 0, trval, StandardCharsets.UTF_8);
638 if (!s.containsKey(enstr))
639 s.put(enstr, trstr);
640 }
641 }
642 }
643 }
644 } catch (IOException e) {
645 Main.trace(e);
646 return false;
647 }
648 if (!s.isEmpty()) {
649 strings = s;
650 pstrings = p;
651 return true;
652 }
653 return false;
654 }
655
656 /**
657 * Sets the default locale (see {@link Locale#setDefault(Locale)} to the local
658 * given by <code>localName</code>.
659 *
660 * Ignored if localeName is null. If the locale with name <code>localName</code>
661 * isn't found the default local is set to <tt>en</tt> (english).
662 *
663 * @param localeName the locale name. Ignored if null.
664 */
665 public static void set(String localeName) {
666 if (localeName != null) {
667 Locale l = LanguageInfo.getLocale(localeName);
668 if (load(LanguageInfo.getJOSMLocaleCode(l))) {
669 Locale.setDefault(l);
670 } else {
671 if (!"en".equals(l.getLanguage())) {
672 Main.info(tr("Unable to find translation for the locale {0}. Reverting to {1}.",
673 LanguageInfo.getDisplayName(l), LanguageInfo.getDisplayName(Locale.getDefault())));
674 } else {
675 strings = null;
676 pstrings = null;
677 }
678 }
679 }
680 }
681
682 /**
683 * Localizations for file chooser dialog.
684 * For some locales (e.g. de, fr) translations are provided
685 * by Java, but not for others (e.g. ru, uk).
686 */
687 public static void translateJavaInternalMessages() {
688 Locale l = Locale.getDefault();
689
690 AbstractFileChooser.setDefaultLocale(l);
691 JFileChooser.setDefaultLocale(l);
692 JColorChooser.setDefaultLocale(l);
693 for (String key : javaInternalMessageKeys) {
694 String us = UIManager.getString(key, Locale.US);
695 String loc = UIManager.getString(key, l);
696 // only provide custom translation if it is not already localized by Java
697 if (us != null && us.equals(loc)) {
698 UIManager.put(key, tr(us));
699 }
700 }
701 }
702
703 private static int pluralEval(long n) {
704 switch(pluralMode) {
705 case MODE_NOTONE: /* bg, da, de, el, en, en_GB, es, et, eu, fi, gl, is, it, iw_IL, nb, nl, sv */
706 return (n != 1) ? 1 : 0;
707 case MODE_NONE: /* id, vi, ja, km, tr, zh_CN, zh_TW */
708 return 0;
709 case MODE_GREATERONE: /* fr, pt_BR */
710 return (n > 1) ? 1 : 0;
711 case MODE_CS:
712 return (n == 1) ? 0 : (((n >= 2) && (n <= 4)) ? 1 : 2);
713 //case MODE_AR:
714 // return ((n == 0) ? 0 : ((n == 1) ? 1 : ((n == 2) ? 2 : ((((n % 100) >= 3)
715 // && ((n % 100) <= 10)) ? 3 : ((((n % 100) >= 11) && ((n % 100) <= 99)) ? 4 : 5)))));
716 case MODE_PL:
717 return (n == 1) ? 0 : (((((n % 10) >= 2) && ((n % 10) <= 4))
718 && (((n % 100) < 10) || ((n % 100) >= 20))) ? 1 : 2);
719 //case MODE_RO:
720 // return ((n == 1) ? 0 : ((((n % 100) > 19) || (((n % 100) == 0) && (n != 0))) ? 2 : 1));
721 case MODE_LT:
722 return ((n % 10) == 1) && ((n % 100) != 11) ? 0 : (((n % 10) >= 2)
723 && (((n % 100) < 10) || ((n % 100) >= 20)) ? 1 : 2);
724 case MODE_RU:
725 return (((n % 10) == 1) && ((n % 100) != 11)) ? 0 : (((((n % 10) >= 2)
726 && ((n % 10) <= 4)) && (((n % 100) < 10) || ((n % 100) >= 20))) ? 1 : 2);
727 case MODE_SK:
728 return (n == 1) ? 1 : (((n >= 2) && (n <= 4)) ? 2 : 0);
729 //case MODE_SL:
730 // return (((n % 100) == 1) ? 1 : (((n % 100) == 2) ? 2 : ((((n % 100) == 3)
731 // || ((n % 100) == 4)) ? 3 : 0)));
732 }
733 return 0;
734 }
735
736 public static TranslationAdapter getTranslationAdapter() {
737 return I18n::tr;
738 }
739
740 /**
741 * Setup special font for Khmer script, as the default Java fonts do not display these characters.
742 *
743 * @since 8282
744 */
745 public static void setupLanguageFonts() {
746 // Use special font for Khmer script, as the default Java font do not display these characters
747 if ("km".equals(LanguageInfo.getJOSMLocaleCode())) {
748 Collection<String> fonts = Arrays.asList(
749 GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames());
750 for (String f : new String[]{"Khmer UI", "DaunPenh", "MoolBoran"}) {
751 if (fonts.contains(f)) {
752 GuiHelper.setUIFont(f);
753 break;
754 }
755 }
756 }
757 }
758}
Note: See TracBrowser for help on using the repository browser.