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}