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