001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.tools;
003
004import java.util.LinkedList;
005import java.util.List;
006import java.util.Locale;
007
008/**
009 * This is a utility class that provides information about locales and allows to convert locale codes.
010 */
011public final class LanguageInfo {
012
013    private LanguageInfo() {
014        // Hide default constructor for utils classes
015    }
016
017    /**
018     * Type of the locale to use
019     * @since 5915
020     */
021    public enum LocaleType {
022        /** The current default language */
023        DEFAULT,
024        /** The current default language, but not english */
025        DEFAULTNOTENGLISH,
026        /** The base language (i.e. pt for pt_BR) */
027        BASELANGUAGE,
028        /** The standard english texts */
029        ENGLISH,
030        /** The locale prefix on the OSM wiki */
031        OSM_WIKI,
032    }
033
034    /**
035     * Replies the wiki language prefix for the given locale. The wiki language
036     * prefix has the form 'Xy:' where 'Xy' is a ISO 639 language code in title
037     * case (or Xy_AB: for sub languages).
038     *
039     * @param type the type
040     * @return the wiki language prefix or {@code null} for {@link LocaleType#BASELANGUAGE}, when
041     * base language is identical to default or english
042     * @since 5915
043     */
044    public static String getWikiLanguagePrefix(LocaleType type) {
045        return getWikiLanguagePrefix(Locale.getDefault(), type);
046    }
047
048    static String getWikiLanguagePrefix(Locale locale, LocaleType type) {
049        if (type == LocaleType.ENGLISH) {
050            return "";
051        } else if (type == LocaleType.OSM_WIKI && Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) {
052            return "";
053        } else if (type == LocaleType.OSM_WIKI && Locale.SIMPLIFIED_CHINESE.equals(locale)) {
054            return "Zh-hans:";
055        } else if (type == LocaleType.OSM_WIKI && Locale.TRADITIONAL_CHINESE.equals(locale)) {
056            return "Zh-hant:";
057        }
058
059        String code = getJOSMLocaleCode(locale);
060
061        if (type == LocaleType.OSM_WIKI) {
062            if (code.matches("[^_@]+[_@][^_]+")) {
063                code = code.substring(0, 2);
064                if ("en".equals(code)) {
065                    return "";
066                }
067            }
068            if ("nb".equals(code)) { /* OSM-Wiki has "no", but no "nb" */
069                return "No:";
070            } else if ("de".equals(code) || "es".equals(code) || "fr".equals(code)
071                    || "it".equals(code) || "nl".equals(code) || "ru".equals(code)
072                    || "ja".equals(code)) {
073                return code.toUpperCase(Locale.ENGLISH) + ":";
074            } else {
075                return code.substring(0, 1).toUpperCase(Locale.ENGLISH) + code.substring(1) + ":";
076            }
077        }
078
079        if (type == LocaleType.BASELANGUAGE) {
080            if (code.matches("[^_]+_[^_]+")) {
081                code = code.substring(0, 2);
082                if ("en".equals(code))
083                    return null;
084            } else {
085                return null;
086            }
087        } else if (type == LocaleType.DEFAULTNOTENGLISH && "en".equals(code)) {
088            return null;
089        } else if (code.matches(".+@.+")) {
090            return code.substring(0, 1).toUpperCase(Locale.ENGLISH)
091                    + code.substring(1, 2)
092                    + '-'
093                    + code.substring(3, 4).toUpperCase(Locale.ENGLISH)
094                    + code.substring(4)
095                    + ':';
096        }
097        return code.substring(0, 1).toUpperCase(Locale.ENGLISH) + code.substring(1) + ':';
098    }
099
100    /**
101     * Replies the wiki language prefix for the current locale.
102     *
103     * @return the wiki language prefix
104     * @see Locale#getDefault()
105     * @see #getWikiLanguagePrefix(LocaleType)
106     */
107    public static String getWikiLanguagePrefix() {
108        return getWikiLanguagePrefix(LocaleType.DEFAULT);
109    }
110
111    /**
112     * Replies the JOSM locale code for the default locale.
113     *
114     * @return the JOSM locale code for the default locale
115     * @see #getJOSMLocaleCode(Locale)
116     */
117    public static String getJOSMLocaleCode() {
118        return getJOSMLocaleCode(Locale.getDefault());
119    }
120
121    /**
122     * Replies the locale code used by JOSM for a given locale.
123     *
124     * In most cases JOSM uses the 2-character ISO 639 language code ({@link Locale#getLanguage()}
125     * to identify the locale of a localized resource, but in some cases it may use the
126     * programmatic name for locales, as replied by {@link Locale#toString()}.
127     *
128     * For unknown country codes and variants this function already does fallback to
129     * internally known translations.
130     *
131     * @param locale the locale. Replies "en" if null.
132     * @return the JOSM code for the given locale
133     */
134    public static String getJOSMLocaleCode(Locale locale) {
135        if (locale == null) return "en";
136        for (String full : getLanguageCodes(locale)) {
137            if ("iw_IL".equals(full))
138                return "he";
139            else if ("in".equals(full))
140                return "id";
141            else if (I18n.hasCode(full)) // catch all non-single codes
142                return full;
143        }
144
145        // return single code as fallback
146        return locale.getLanguage();
147    }
148
149    /**
150     * Replies the locale code used by Java for a given locale.
151     *
152     * In most cases JOSM and Java uses the same codes, but for some exceptions this is needed.
153     *
154     * @param localeName the locale. Replies "en" if null.
155     * @return the Java code for the given locale
156     * @since 8232
157     */
158    public static String getJavaLocaleCode(String localeName) {
159        if (localeName == null) return "en";
160        if ("ca@valencia".equals(localeName)) {
161            localeName = "ca__valencia";
162        } else if ("he".equals(localeName)) {
163            localeName = "iw_IL";
164        } else if ("id".equals(localeName)) {
165            localeName = "in";
166        }
167        return localeName;
168    }
169
170    /**
171     * Replies the display string used by JOSM for a given locale.
172     *
173     * In most cases returns text replied by {@link Locale#getDisplayName()}, for some
174     * locales an override is used (i.e. when unsupported by Java).
175     *
176     * @param locale the locale. Replies "en" if null.
177     * @return the display string for the given locale
178     * @since 8232
179     */
180    public static String getDisplayName(Locale locale) {
181        String currentCountry = Locale.getDefault().getCountry();
182        String localeCountry = locale.getCountry();
183        // Don't display locale country if country has been forced to current country at JOSM startup
184        if (currentCountry.equals(localeCountry) && !I18n.hasCode(getLanguageCodes(locale).get(0))) {
185            return new Locale(locale.getLanguage(), "", locale.getVariant()).getDisplayName();
186        }
187        return locale.getDisplayName();
188    }
189
190    /**
191     * Replies the locale used by Java for a given language code.
192     *
193     * Accepts JOSM and Java codes as input.
194     *
195     * @param localeName the locale code.
196     * @return the resulting locale
197     */
198    public static Locale getLocale(String localeName) {
199        return getLocale(localeName, false);
200    }
201
202    /**
203     * Replies the locale used by Java for a given language code.
204     *
205     * Accepts JOSM and Java codes as input.
206     *
207     * @param localeName the locale code.
208     * @param useDefaultCountry if {@code true}, the current locale country will be used if no country is specified
209     * @return the resulting locale
210     * @since 15547
211     */
212    public static Locale getLocale(String localeName, boolean useDefaultCountry) {
213        int country = localeName.indexOf('_');
214        int variant = localeName.indexOf('@');
215        if (variant < 0 && country >= 0)
216            variant = localeName.indexOf('_', country+1);
217        Locale l;
218        if (variant > 0 && country > 0) {
219            l = new Locale(localeName.substring(0, country), localeName.substring(country+1, variant), localeName.substring(variant + 1));
220        } else if (variant > 0) {
221            l = new Locale(localeName.substring(0, variant), "", localeName.substring(variant + 1));
222        } else if (country > 0) {
223            l = new Locale(localeName.substring(0, country), localeName.substring(country + 1));
224        } else {
225            l = new Locale(localeName, useDefaultCountry ? Locale.getDefault().getCountry() : "");
226        }
227        return l;
228    }
229
230    /**
231     * Check if a new language is better than a previous existing. Can be used in classes where
232     * multiple user supplied language marked strings appear and the best one is searched. Following
233     * priorities: current language, english, any other
234     *
235     * @param oldLanguage the language code of the existing string
236     * @param newLanguage the language code of the new string
237     * @return true if new one is better
238     * @since 8091
239     */
240    public static boolean isBetterLanguage(String oldLanguage, String newLanguage) {
241        if (oldLanguage == null)
242            return true;
243        String want = getJOSMLocaleCode();
244        return want.equals(newLanguage) || (!want.equals(oldLanguage) && newLanguage.startsWith("en"));
245    }
246
247    /**
248     * Replies the language prefix for use in XML elements (with a dot appended).
249     *
250     * @return the XML language prefix
251     * @see #getJOSMLocaleCode()
252     */
253    public static String getLanguageCodeXML() {
254        String code = getJOSMLocaleCode();
255        code = code.replace('@', '-');
256        return code+'.';
257    }
258
259    /**
260     * Replies the language prefix for use in manifests (with an underscore appended).
261     *
262     * @return the manifest language prefix
263     * @see #getJOSMLocaleCode()
264     */
265    public static String getLanguageCodeManifest() {
266        String code = getJOSMLocaleCode();
267        code = code.replace('@', '-');
268        return code+'_';
269    }
270
271    /**
272     * Replies a list of language codes for local names. Prefixes range from very specific
273     * to more generic.
274     * <ul>
275     *   <li>lang_COUNTRY@variant  of the current locale</li>
276     *   <li>lang@variant  of the current locale</li>
277     *   <li>lang_COUNTRY of the current locale</li>
278     *   <li>lang of the current locale</li>
279     * </ul>
280     *
281     * @param l the locale to use, <code>null</code> for default locale
282     * @return list of codes
283     * @since 8283
284     */
285    public static List<String> getLanguageCodes(Locale l) {
286        List<String> list = new LinkedList<>();
287        if (l == null)
288            l = Locale.getDefault();
289        String lang = l.getLanguage();
290        String c = l.getCountry();
291        String v = l.getVariant();
292        if (c.isEmpty())
293            c = null;
294        if (v != null && !v.isEmpty()) {
295            if (c != null)
296                list.add(lang+'_'+c+'@'+v);
297            list.add(lang+'@'+v);
298        }
299        if (c != null)
300            list.add(lang+'_'+c);
301        list.add(lang);
302        return list;
303    }
304}