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

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

see #11924 - add assertions to check if java.locale.providers enables CLDR as default because of JEP 252 in Java 9 (unit tests depending on default Java date formatting are failing)

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