001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import java.io.BufferedInputStream; 005import java.io.File; 006import java.io.FileInputStream; 007import java.io.IOException; 008import java.io.InputStream; 009import java.net.URL; 010import java.nio.charset.StandardCharsets; 011import java.text.MessageFormat; 012import java.util.ArrayList; 013import java.util.Arrays; 014import java.util.Collection; 015import java.util.Comparator; 016import java.util.HashMap; 017import java.util.Locale; 018import java.util.Map; 019import java.util.jar.JarInputStream; 020import java.util.zip.ZipEntry; 021 022import javax.swing.JColorChooser; 023import javax.swing.JFileChooser; 024import javax.swing.UIManager; 025 026import org.openstreetmap.gui.jmapviewer.FeatureAdapter.TranslationAdapter; 027import org.openstreetmap.josm.Main; 028 029/** 030 * Internationalisation support. 031 * 032 * @author Immanuel.Scholz 033 */ 034public final class I18n { 035 036 private I18n() { 037 // Hide default constructor for utils classes 038 } 039 040 private enum PluralMode { MODE_NOTONE, MODE_NONE, MODE_GREATERONE, 041 MODE_CS/*, MODE_AR*/, MODE_PL/*, MODE_RO*/, MODE_RU, MODE_SK/*, MODE_SL*/} 042 private static PluralMode pluralMode = PluralMode.MODE_NOTONE; /* english default */ 043 private static String loadedCode = "en"; 044 045 /* Localization keys for file chooser (and color chooser). */ 046 private static final String[] javaInternalMessageKeys = new String[] { 047 /* JFileChooser windows laf */ 048 "FileChooser.detailsViewActionLabelText", 049 "FileChooser.detailsViewButtonAccessibleName", 050 "FileChooser.detailsViewButtonToolTipText", 051 "FileChooser.fileAttrHeaderText", 052 "FileChooser.fileDateHeaderText", 053 "FileChooser.fileNameHeaderText", 054 "FileChooser.fileNameLabelText", 055 "FileChooser.fileSizeHeaderText", 056 "FileChooser.fileTypeHeaderText", 057 "FileChooser.filesOfTypeLabelText", 058 "FileChooser.homeFolderAccessibleName", 059 "FileChooser.homeFolderToolTipText", 060 "FileChooser.listViewActionLabelText", 061 "FileChooser.listViewButtonAccessibleName", 062 "FileChooser.listViewButtonToolTipText", 063 "FileChooser.lookInLabelText", 064 "FileChooser.newFolderAccessibleName", 065 "FileChooser.newFolderActionLabelText", 066 "FileChooser.newFolderToolTipText", 067 "FileChooser.refreshActionLabelText", 068 "FileChooser.saveInLabelText", 069 "FileChooser.upFolderAccessibleName", 070 "FileChooser.upFolderToolTipText", 071 "FileChooser.viewMenuLabelText", 072 073 /* JFileChooser gtk laf */ 074 "FileChooser.acceptAllFileFilterText", 075 "FileChooser.cancelButtonText", 076 "FileChooser.cancelButtonToolTipText", 077 "FileChooser.deleteFileButtonText", 078 "FileChooser.filesLabelText", 079 "FileChooser.filterLabelText", 080 "FileChooser.foldersLabelText", 081 "FileChooser.newFolderButtonText", 082 "FileChooser.newFolderDialogText", 083 "FileChooser.openButtonText", 084 "FileChooser.openButtonToolTipText", 085 "FileChooser.openDialogTitleText", 086 "FileChooser.pathLabelText", 087 "FileChooser.renameFileButtonText", 088 "FileChooser.renameFileDialogText", 089 "FileChooser.renameFileErrorText", 090 "FileChooser.renameFileErrorTitle", 091 "FileChooser.saveButtonText", 092 "FileChooser.saveButtonToolTipText", 093 "FileChooser.saveDialogTitleText", 094 095 /* JFileChooser motif laf */ 096 //"FileChooser.cancelButtonText", 097 //"FileChooser.cancelButtonToolTipText", 098 "FileChooser.enterFileNameLabelText", 099 //"FileChooser.filesLabelText", 100 //"FileChooser.filterLabelText", 101 //"FileChooser.foldersLabelText", 102 "FileChooser.helpButtonText", 103 "FileChooser.helpButtonToolTipText", 104 //"FileChooser.openButtonText", 105 //"FileChooser.openButtonToolTipText", 106 //"FileChooser.openDialogTitleText", 107 //"FileChooser.pathLabelText", 108 //"FileChooser.saveButtonText", 109 //"FileChooser.saveButtonToolTipText", 110 //"FileChooser.saveDialogTitleText", 111 "FileChooser.updateButtonText", 112 "FileChooser.updateButtonToolTipText", 113 114 /* gtk color chooser */ 115 "GTKColorChooserPanel.blueText", 116 "GTKColorChooserPanel.colorNameText", 117 "GTKColorChooserPanel.greenText", 118 "GTKColorChooserPanel.hueText", 119 "GTKColorChooserPanel.nameText", 120 "GTKColorChooserPanel.redText", 121 "GTKColorChooserPanel.saturationText", 122 "GTKColorChooserPanel.valueText", 123 124 /* JOptionPane */ 125 "OptionPane.okButtonText", 126 "OptionPane.yesButtonText", 127 "OptionPane.noButtonText", 128 "OptionPane.cancelButtonText" 129 }; 130 private static Map<String, String> strings = null; 131 private static Map<String, String[]> pstrings = null; 132 private static Map<String, PluralMode> languages = new HashMap<>(); 133 134 /** 135 * Translates some text for the current locale. 136 * These strings are collected by a script that runs on the source code files. 137 * After translation, the localizations are distributed with the main program. 138 * <br> 139 * For example, {@code tr("JOSM''s default value is ''{0}''.", val)}. 140 * <br> 141 * Use {@link #trn} for distinguishing singular from plural text, i.e., 142 * do not use {@code tr(size == 1 ? "singular" : "plural")} nor 143 * {@code size == 1 ? tr("singular") : tr("plural")} 144 * 145 * @param text the text to translate. 146 * Must be a string literal. (No constants or local vars.) 147 * Can be broken over multiple lines. 148 * An apostrophe ' must be quoted by another apostrophe. 149 * @param objects the parameters for the string. 150 * Mark occurrences in {@code text} with {@code {0}}, {@code {1}}, ... 151 * @return the translated string. 152 * @see #trn 153 * @see #trc 154 * @see #trnc 155 */ 156 public static final String tr(String text, Object... objects) { 157 if (text == null) return null; 158 return MessageFormat.format(gettext(text, null), objects); 159 } 160 161 /** 162 * Translates some text in a context for the current locale. 163 * There can be different translations for the same text within different contexts. 164 * 165 * @param context string that helps translators to find an appropriate 166 * translation for {@code text}. 167 * @param text the text to translate. 168 * @return the translated string. 169 * @see #tr 170 * @see #trn 171 * @see #trnc 172 */ 173 public static final String trc(String context, String text) { 174 if (context == null) 175 return tr(text); 176 if (text == null) 177 return null; 178 return MessageFormat.format(gettext(text, context), (Object)null); 179 } 180 181 public static final String trc_lazy(String context, String text) { 182 if (context == null) 183 return tr(text); 184 if (text == null) 185 return null; 186 return MessageFormat.format(gettext_lazy(text, context), (Object)null); 187 } 188 189 /** 190 * Marks a string for translation (such that a script can harvest 191 * the translatable strings from the source files). 192 * 193 * For example, {@code 194 * String[] options = new String[] {marktr("up"), marktr("down")}; 195 * lbl.setText(tr(options[0]));} 196 * @param text the string to be marked for translation. 197 * @return {@code text} unmodified. 198 */ 199 public static final String marktr(String text) { 200 return text; 201 } 202 203 public static final String marktrc(String context, String text) { 204 return text; 205 } 206 207 /** 208 * Translates some text for the current locale and distinguishes between 209 * {@code singularText} and {@code pluralText} depending on {@code n}. 210 * <br> 211 * For instance, {@code trn("There was an error!", "There were errors!", i)} or 212 * {@code trn("Found {0} error in {1}!", "Found {0} errors in {1}!", i, Integer.toString(i), url)}. 213 * 214 * @param singularText the singular text to translate. 215 * Must be a string literal. (No constants or local vars.) 216 * Can be broken over multiple lines. 217 * An apostrophe ' must be quoted by another apostrophe. 218 * @param pluralText the plural text to translate. 219 * Must be a string literal. (No constants or local vars.) 220 * Can be broken over multiple lines. 221 * An apostrophe ' must be quoted by another apostrophe. 222 * @param n a number to determine whether {@code singularText} or {@code pluralText} is used. 223 * @param objects the parameters for the string. 224 * Mark occurrences in {@code singularText} and {@code pluralText} with {@code {0}}, {@code {1}}, ... 225 * @return the translated string. 226 * @see #tr 227 * @see #trc 228 * @see #trnc 229 */ 230 public static final String trn(String singularText, String pluralText, long n, Object... objects) { 231 return MessageFormat.format(gettextn(singularText, pluralText, null, n), objects); 232 } 233 234 /** 235 * Translates some text in a context for the current locale and distinguishes between 236 * {@code singularText} and {@code pluralText} depending on {@code n}. 237 * There can be different translations for the same text within different contexts. 238 * 239 * @param context string that helps translators to find an appropriate 240 * translation for {@code text}. 241 * @param singularText the singular text to translate. 242 * Must be a string literal. (No constants or local vars.) 243 * Can be broken over multiple lines. 244 * An apostrophe ' must be quoted by another apostrophe. 245 * @param pluralText the plural text to translate. 246 * Must be a string literal. (No constants or local vars.) 247 * Can be broken over multiple lines. 248 * An apostrophe ' must be quoted by another apostrophe. 249 * @param n a number to determine whether {@code singularText} or {@code pluralText} is used. 250 * @param objects the parameters for the string. 251 * Mark occurrences in {@code singularText} and {@code pluralText} with {@code {0}}, {@code {1}}, ... 252 * @return the translated string. 253 * @see #tr 254 * @see #trc 255 * @see #trn 256 */ 257 public static final String trnc(String context, String singularText, String pluralText, long n, Object... objects) { 258 return MessageFormat.format(gettextn(singularText, pluralText, context, n), objects); 259 } 260 261 private static final String gettext(String text, String ctx, boolean lazy) 262 { 263 int i; 264 if(ctx == null && text.startsWith("_:") && (i = text.indexOf('\n')) >= 0) 265 { 266 ctx = text.substring(2,i-1); 267 text = text.substring(i+1); 268 } 269 if(strings != null) 270 { 271 String trans = strings.get(ctx == null ? text : "_:"+ctx+"\n"+text); 272 if(trans != null) 273 return trans; 274 } 275 if(pstrings != null) { 276 i = pluralEval(1); 277 String[] trans = pstrings.get(ctx == null ? text : "_:"+ctx+"\n"+text); 278 if(trans != null && trans.length > i) 279 return trans[i]; 280 } 281 return lazy ? gettext(text, null) : text; 282 } 283 284 private static final String gettext(String text, String ctx) { 285 return gettext(text, ctx, false); 286 } 287 288 289 /* try without context, when context try fails */ 290 private static final String gettext_lazy(String text, String ctx) { 291 return gettext(text, ctx, true); 292 } 293 294 private static final String gettextn(String text, String plural, String ctx, long num) 295 { 296 int i; 297 if(ctx == null && text.startsWith("_:") && (i = text.indexOf('\n')) >= 0) 298 { 299 ctx = text.substring(2,i-1); 300 text = text.substring(i+1); 301 } 302 if(pstrings != null) 303 { 304 i = pluralEval(num); 305 String[] trans = pstrings.get(ctx == null ? text : "_:"+ctx+"\n"+text); 306 if(trans != null && trans.length > i) 307 return trans[i]; 308 } 309 310 return num == 1 ? text : plural; 311 } 312 313 public static String escape(String msg) { 314 if (msg == null) return null; 315 return msg.replace("\'", "\'\'").replace("{", "\'{\'").replace("}", "\'}\'"); 316 } 317 318 private static URL getTranslationFile(String lang) { 319 return Main.class.getResource("/data/"+lang+".lang"); 320 } 321 322 /** 323 * Get a list of all available JOSM Translations. 324 * @return an array of locale objects. 325 */ 326 public static final Locale[] getAvailableTranslations() { 327 Collection<Locale> v = new ArrayList<>(languages.size()); 328 if(getTranslationFile("en") != null) 329 { 330 for (String loc : languages.keySet()) { 331 if(getTranslationFile(loc) != null) { 332 v.add(LanguageInfo.getLocale(loc)); 333 } 334 } 335 } 336 v.add(Locale.ENGLISH); 337 Locale[] l = new Locale[v.size()]; 338 l = v.toArray(l); 339 Arrays.sort(l, new Comparator<Locale>() { 340 @Override 341 public int compare(Locale o1, Locale o2) { 342 return o1.toString().compareTo(o2.toString()); 343 } 344 }); 345 return l; 346 } 347 348 public static boolean hasCode(String code) 349 { 350 return languages.containsKey(code); 351 } 352 353 public static void init() 354 { 355 //languages.put("ar", PluralMode.MODE_AR); 356 languages.put("bg", PluralMode.MODE_NOTONE); 357 languages.put("ca", PluralMode.MODE_NOTONE); 358 languages.put("cs", PluralMode.MODE_CS); 359 languages.put("da", PluralMode.MODE_NOTONE); 360 languages.put("de", PluralMode.MODE_NOTONE); 361 languages.put("el", PluralMode.MODE_NOTONE); 362 languages.put("en_AU", PluralMode.MODE_NOTONE); 363 languages.put("en_GB", PluralMode.MODE_NOTONE); 364 languages.put("es", PluralMode.MODE_NOTONE); 365 languages.put("et", PluralMode.MODE_NOTONE); 366 //languages.put("eu", PluralMode.MODE_NOTONE); 367 languages.put("fi", PluralMode.MODE_NOTONE); 368 languages.put("fr", PluralMode.MODE_GREATERONE); 369 languages.put("gl", PluralMode.MODE_NOTONE); 370 //languages.put("he", PluralMode.MODE_NOTONE); 371 languages.put("hu", PluralMode.MODE_NOTONE); 372 languages.put("id", PluralMode.MODE_NONE); 373 //languages.put("is", PluralMode.MODE_NOTONE); 374 languages.put("it", PluralMode.MODE_NOTONE); 375 languages.put("ja", PluralMode.MODE_NONE); 376 //languages.put("nb", PluralMode.MODE_NOTONE); 377 languages.put("nl", PluralMode.MODE_NOTONE); 378 languages.put("pl", PluralMode.MODE_PL); 379 languages.put("pt", PluralMode.MODE_NOTONE); 380 languages.put("pt_BR", PluralMode.MODE_GREATERONE); 381 //languages.put("ro", PluralMode.MODE_RO); 382 languages.put("ru", PluralMode.MODE_RU); 383 languages.put("sk", PluralMode.MODE_SK); 384 //languages.put("sl", PluralMode.MODE_SL); 385 languages.put("sv", PluralMode.MODE_NOTONE); 386 //languages.put("tr", PluralMode.MODE_NONE); 387 languages.put("uk", PluralMode.MODE_RU); 388 languages.put("zh_CN", PluralMode.MODE_NONE); 389 languages.put("zh_TW", PluralMode.MODE_NONE); 390 391 /* try initial language settings, may be changed later again */ 392 if(!load(Locale.getDefault().toString())) { 393 Locale.setDefault(Locale.ENGLISH); 394 } 395 } 396 397 public static void addTexts(File source) { 398 if ("en".equals(loadedCode)) 399 return; 400 String enfile = "data/en.lang"; 401 String langfile = "data/"+loadedCode+".lang"; 402 try ( 403 FileInputStream fis = new FileInputStream(source); 404 JarInputStream jar = new JarInputStream(fis) 405 ) { 406 ZipEntry e; 407 boolean found = false; 408 while (!found && (e = jar.getNextEntry()) != null) { 409 String name = e.getName(); 410 if(name.equals(enfile)) 411 found = true; 412 } 413 if (found) { 414 try ( 415 FileInputStream fisTrans = new FileInputStream(source); 416 JarInputStream jarTrans = new JarInputStream(fisTrans) 417 ) { 418 found = false; 419 while(!found && (e = jarTrans.getNextEntry()) != null) { 420 String name = e.getName(); 421 if (name.equals(langfile)) 422 found = true; 423 } 424 if (found) 425 load(jar, jarTrans, true); 426 } 427 } 428 } catch (IOException e) { 429 // Ignore 430 } 431 } 432 433 private static boolean load(String l) { 434 if ("en".equals(l) || "en_US".equals(l)) { 435 strings = null; 436 pstrings = null; 437 loadedCode = "en"; 438 pluralMode = PluralMode.MODE_NOTONE; 439 return true; 440 } 441 URL en = getTranslationFile("en"); 442 if (en == null) 443 return false; 444 URL tr = getTranslationFile(l); 445 if (tr == null || !languages.containsKey(l)) { 446 int i = l.indexOf('_'); 447 if (i > 0) { 448 l = l.substring(0, i); 449 } 450 tr = getTranslationFile(l); 451 if (tr == null || !languages.containsKey(l)) 452 return false; 453 } 454 try ( 455 InputStream enStream = en.openStream(); 456 InputStream trStream = tr.openStream() 457 ) { 458 if (load(enStream, trStream, false)) { 459 pluralMode = languages.get(l); 460 loadedCode = l; 461 return true; 462 } 463 } catch (IOException e) { 464 // Ignore exception 465 } 466 return false; 467 } 468 469 private static boolean load(InputStream en, InputStream tr, boolean add) { 470 Map<String, String> s; 471 Map<String, String[]> p; 472 if (add) { 473 s = strings; 474 p = pstrings; 475 } else { 476 s = new HashMap<>(); 477 p = new HashMap<>(); 478 } 479 /* file format: 480 Files are always a group. English file and translated file must provide identical datasets. 481 482 for all single strings: 483 { 484 unsigned short (2 byte) stringlength 485 - length 0 indicates missing translation 486 - length 0xFFFE indicates translation equal to original, but otherwise is equal to length 0 487 string 488 } 489 unsigned short (2 byte) 0xFFFF (marks end of single strings) 490 for all multi strings: 491 { 492 unsigned char (1 byte) stringcount 493 - count 0 indicates missing translations 494 - count 0xFE indicates translations equal to original, but otherwise is equal to length 0 495 for stringcount 496 unsigned short (2 byte) stringlength 497 string 498 } 499 */ 500 try 501 { 502 InputStream ens = new BufferedInputStream(en); 503 InputStream trs = new BufferedInputStream(tr); 504 byte[] enlen = new byte[2]; 505 byte[] trlen = new byte[2]; 506 boolean multimode = false; 507 byte[] str = new byte[4096]; 508 for(;;) 509 { 510 if(multimode) 511 { 512 int ennum = ens.read(); 513 int trnum = trs.read(); 514 if(trnum == 0xFE) /* marks identical string, handle equally to non-translated */ 515 trnum = 0; 516 if((ennum == -1 && trnum != -1) || (ennum != -1 && trnum == -1)) /* files do not match */ 517 return false; 518 if(ennum == -1) { 519 break; 520 } 521 String[] enstrings = new String[ennum]; 522 String[] trstrings = new String[trnum]; 523 for(int i = 0; i < ennum; ++i) 524 { 525 int val = ens.read(enlen); 526 if(val != 2) /* file corrupt */ 527 return false; 528 val = (enlen[0] < 0 ? 256+enlen[0]:enlen[0])*256+(enlen[1] < 0 ? 256+enlen[1]:enlen[1]); 529 if(val > str.length) { 530 str = new byte[val]; 531 } 532 int rval = ens.read(str, 0, val); 533 if(rval != val) /* file corrupt */ 534 return false; 535 enstrings[i] = new String(str, 0, val, StandardCharsets.UTF_8); 536 } 537 for(int i = 0; i < trnum; ++i) 538 { 539 int val = trs.read(trlen); 540 if(val != 2) /* file corrupt */ 541 return false; 542 val = (trlen[0] < 0 ? 256+trlen[0]:trlen[0])*256+(trlen[1] < 0 ? 256+trlen[1]:trlen[1]); 543 if(val > str.length) { 544 str = new byte[val]; 545 } 546 int rval = trs.read(str, 0, val); 547 if(rval != val) /* file corrupt */ 548 return false; 549 trstrings[i] = new String(str, 0, val, StandardCharsets.UTF_8); 550 } 551 if(trnum > 0 && !p.containsKey(enstrings[0])) { 552 p.put(enstrings[0], trstrings); 553 } 554 } 555 else 556 { 557 int enval = ens.read(enlen); 558 int trval = trs.read(trlen); 559 if(enval != trval) /* files do not match */ 560 return false; 561 if(enval == -1) { 562 break; 563 } 564 if(enval != 2) /* files corrupt */ 565 return false; 566 enval = (enlen[0] < 0 ? 256+enlen[0]:enlen[0])*256+(enlen[1] < 0 ? 256+enlen[1]:enlen[1]); 567 trval = (trlen[0] < 0 ? 256+trlen[0]:trlen[0])*256+(trlen[1] < 0 ? 256+trlen[1]:trlen[1]); 568 if(trval == 0xFFFE) /* marks identical string, handle equally to non-translated */ 569 trval = 0; 570 if(enval == 0xFFFF) 571 { 572 multimode = true; 573 if(trval != 0xFFFF) /* files do not match */ 574 return false; 575 } else { 576 if (enval > str.length) { 577 str = new byte[enval]; 578 } 579 if (trval > str.length) { 580 str = new byte[trval]; 581 } 582 int val = ens.read(str, 0, enval); 583 if(val != enval) /* file corrupt */ 584 return false; 585 String enstr = new String(str, 0, enval, StandardCharsets.UTF_8); 586 if (trval != 0) { 587 val = trs.read(str, 0, trval); 588 if(val != trval) /* file corrupt */ 589 return false; 590 String trstr = new String(str, 0, trval, StandardCharsets.UTF_8); 591 if(!s.containsKey(enstr)) 592 s.put(enstr, trstr); 593 } 594 } 595 } 596 } 597 } 598 catch (IOException e) { 599 return false; 600 } 601 if (!s.isEmpty()) { 602 strings = s; 603 pstrings = p; 604 return true; 605 } 606 return false; 607 } 608 609 /** 610 * Sets the default locale (see {@link Locale#setDefault(Locale)} to the local 611 * given by <code>localName</code>. 612 * 613 * Ignored if localeName is null. If the locale with name <code>localName</code> 614 * isn't found the default local is set to <tt>en</tt> (english). 615 * 616 * @param localeName the locale name. Ignored if null. 617 */ 618 public static void set(String localeName){ 619 if (localeName != null) { 620 Locale l = LanguageInfo.getLocale(localeName); 621 if (load(LanguageInfo.getJOSMLocaleCode(l))) { 622 Locale.setDefault(l); 623 } else { 624 if (!"en".equals(l.getLanguage())) { 625 Main.info(tr("Unable to find translation for the locale {0}. Reverting to {1}.", 626 l.getDisplayName(), Locale.getDefault().getDisplayName())); 627 } else { 628 strings = null; 629 pstrings = null; 630 } 631 } 632 } 633 } 634 635 /** 636 * Localizations for file chooser dialog. 637 * For some locales (e.g. de, fr) translations are provided 638 * by Java, but not for others (e.g. ru, uk). 639 */ 640 public static void translateJavaInternalMessages() { 641 Locale l = Locale.getDefault(); 642 643 JFileChooser.setDefaultLocale(l); 644 JColorChooser.setDefaultLocale(l); 645 for (String key : javaInternalMessageKeys) { 646 String us = UIManager.getString(key, Locale.US); 647 String loc = UIManager.getString(key, l); 648 // only provide custom translation if it is not already localized by Java 649 if (us != null && us.equals(loc)) { 650 UIManager.put(key, tr(us)); 651 } 652 } 653 } 654 655 private static int pluralEval(long n) 656 { 657 switch(pluralMode) 658 { 659 case MODE_NOTONE: /* bg, da, de, el, en, en_GB, es, et, eu, fi, gl, is, it, iw_IL, nb, nl, sv */ 660 return ((n != 1) ? 1 : 0); 661 case MODE_NONE: /* ja, tr, zh_CN, zh_TW */ 662 return 0; 663 case MODE_GREATERONE: /* fr, pt_BR */ 664 return ((n > 1) ? 1 : 0); 665 case MODE_CS: 666 return ((n == 1) ? 0 : (((n >= 2) && (n <= 4)) ? 1 : 2)); 667 //case MODE_AR: 668 // return ((n == 0) ? 0 : ((n == 1) ? 1 : ((n == 2) ? 2 : ((((n % 100) >= 3) 669 // && ((n % 100) <= 10)) ? 3 : ((((n % 100) >= 11) && ((n % 100) <= 99)) ? 4 : 5))))); 670 case MODE_PL: 671 return ((n == 1) ? 0 : (((((n % 10) >= 2) && ((n % 10) <= 4)) 672 && (((n % 100) < 10) || ((n % 100) >= 20))) ? 1 : 2)); 673 //case MODE_RO: 674 // return ((n == 1) ? 0 : ((((n % 100) > 19) || (((n % 100) == 0) && (n != 0))) ? 2 : 1)); 675 case MODE_RU: 676 return ((((n % 10) == 1) && ((n % 100) != 11)) ? 0 : (((((n % 10) >= 2) 677 && ((n % 10) <= 4)) && (((n % 100) < 10) || ((n % 100) >= 20))) ? 1 : 2)); 678 case MODE_SK: 679 return ((n == 1) ? 1 : (((n >= 2) && (n <= 4)) ? 2 : 0)); 680 //case MODE_SL: 681 // return (((n % 100) == 1) ? 1 : (((n % 100) == 2) ? 2 : ((((n % 100) == 3) 682 // || ((n % 100) == 4)) ? 3 : 0))); 683 } 684 return 0; 685 } 686 687 public static TranslationAdapter getTranslationAdapter() { 688 return new TranslationAdapter() { 689 @Override 690 public String tr(String text, Object... objects) { 691 return I18n.tr(text, objects); 692 } 693 }; 694 } 695}