001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import java.awt.GraphicsEnvironment; 005import java.io.BufferedInputStream; 006import java.io.File; 007import java.io.FileInputStream; 008import java.io.IOException; 009import java.io.InputStream; 010import java.lang.annotation.Retention; 011import java.lang.annotation.RetentionPolicy; 012import java.net.URL; 013import java.nio.charset.StandardCharsets; 014import java.text.MessageFormat; 015import java.util.ArrayList; 016import java.util.Arrays; 017import java.util.Collection; 018import java.util.Comparator; 019import java.util.HashMap; 020import java.util.Locale; 021import java.util.Map; 022import java.util.jar.JarInputStream; 023import java.util.zip.ZipEntry; 024 025import javax.swing.JColorChooser; 026import javax.swing.JFileChooser; 027import javax.swing.UIManager; 028 029import org.openstreetmap.gui.jmapviewer.FeatureAdapter.TranslationAdapter; 030import org.openstreetmap.josm.Main; 031import org.openstreetmap.josm.gui.util.GuiHelper; 032import org.openstreetmap.josm.gui.widgets.AbstractFileChooser; 033 034/** 035 * Internationalisation support. 036 * 037 * @author Immanuel.Scholz 038 */ 039public final class I18n { 040 041 /** 042 * This annotates strings which do not permit a clean i18n. This is mostly due to strings 043 * containing two nouns which can occur in singular or plural form. 044 * <br> 045 * No behaviour is associated with this annotation. 046 */ 047 @Retention(RetentionPolicy.SOURCE) 048 public @interface QuirkyPluralString { 049 } 050 051 private I18n() { 052 // Hide default constructor for utils classes 053 } 054 055 /** 056 * Enumeration of possible plural modes. It allows us to identify and implement logical conditions of 057 * plural forms defined on <a href="https://help.launchpad.net/Translations/PluralForms">Launchpad</a>. 058 * See <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html">CLDR</a> 059 * for another complete list. 060 * @see #pluralEval 061 */ 062 private enum PluralMode { 063 /** Plural = Not 1. This is the default for many languages, including English: 1 day, but 0 days or 2 days. */ 064 MODE_NOTONE, 065 /** No plural. Mainly for Asian languages (Indonesian, Chinese, Japanese, ...) */ 066 MODE_NONE, 067 /** Plural = Greater than 1. For some latin languages (French, Brazilian Portuguese) */ 068 MODE_GREATERONE, 069 /* Special mode for 070 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ar">Arabic</a>.* 071 MODE_AR,*/ 072 /** Special mode for 073 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#cs">Czech</a>. */ 074 MODE_CS, 075 /** Special mode for 076 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#pl">Polish</a>. */ 077 MODE_PL, 078 /* Special mode for 079 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ro">Romanian</a>.* 080 MODE_RO,*/ 081 /** Special mode for 082 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#lt">Lithuanian</a>. */ 083 MODE_LT, 084 /** Special mode for 085 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ru">Russian</a>. */ 086 MODE_RU, 087 /** Special mode for 088 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#sk">Slovak</a>. */ 089 MODE_SK, 090 /* Special mode for 091 * <a href="http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#sl">Slovenian</a>.* 092 MODE_SL,*/ 093 } 094 095 private static volatile PluralMode pluralMode = PluralMode.MODE_NOTONE; /* english default */ 096 private static volatile String loadedCode = "en"; 097 098 /* Localization keys for file chooser (and color chooser). */ 099 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}