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