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