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

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

see #11390 - sonar - squid:S1604 - Java 8: Anonymous inner classes containing only one method should become lambdas

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