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