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}