001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import java.util.ArrayList; 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 switch (code) { 069 case "nb": /* OSM-Wiki has "no", but no "nb" */ 070 return "No:"; 071 case "sr@latin": /* OSM-Wiki has "Sr-latn" and not Sr-latin */ 072 return "Sr-latn:"; 073 case "de": 074 case "es": 075 case "fr": 076 case "it": 077 case "nl": 078 case "ru": 079 case "ja": 080 return code.toUpperCase(Locale.ENGLISH) + ":"; 081 default: 082 return code.substring(0, 1).toUpperCase(Locale.ENGLISH) + code.substring(1) + ":"; 083 } 084 } 085 086 if (type == LocaleType.BASELANGUAGE) { 087 if (code.matches("[^_]+_[^_]+")) { 088 code = code.substring(0, 2); 089 if ("en".equals(code)) 090 return null; 091 } else { 092 return null; 093 } 094 } else if (type == LocaleType.DEFAULTNOTENGLISH && "en".equals(code)) { 095 return null; 096 } else if (code.matches(".+@.+")) { 097 return code.substring(0, 1).toUpperCase(Locale.ENGLISH) 098 + code.substring(1, 2) 099 + '-' 100 + code.substring(3, 4).toUpperCase(Locale.ENGLISH) 101 + code.substring(4) 102 + ':'; 103 } 104 return code.substring(0, 1).toUpperCase(Locale.ENGLISH) + code.substring(1) + ':'; 105 } 106 107 /** 108 * Replies the wiki language prefix for the current locale. 109 * 110 * @return the wiki language prefix 111 * @see Locale#getDefault() 112 * @see #getWikiLanguagePrefix(LocaleType) 113 */ 114 public static String getWikiLanguagePrefix() { 115 return getWikiLanguagePrefix(LocaleType.DEFAULT); 116 } 117 118 /** 119 * Replies the JOSM locale code for the default locale. 120 * 121 * @return the JOSM locale code for the default locale 122 * @see #getJOSMLocaleCode(Locale) 123 */ 124 public static String getJOSMLocaleCode() { 125 return getJOSMLocaleCode(Locale.getDefault()); 126 } 127 128 /** 129 * Replies the locale code used by JOSM for a given locale. 130 * 131 * In most cases JOSM uses the 2-character ISO 639 language code ({@link Locale#getLanguage()} 132 * to identify the locale of a localized resource, but in some cases it may use the 133 * programmatic name for locales, as replied by {@link Locale#toString()}. 134 * 135 * For unknown country codes and variants this function already does fallback to 136 * internally known translations. 137 * 138 * @param locale the locale. Replies "en" if null. 139 * @return the JOSM code for the given locale 140 */ 141 public static String getJOSMLocaleCode(Locale locale) { 142 if (locale == null) return "en"; 143 for (String full : getLanguageCodes(locale)) { 144 if ("iw_IL".equals(full)) 145 return "he"; 146 else if ("in".equals(full)) 147 return "id"; 148 else if (I18n.hasCode(full)) // catch all non-single codes 149 return full; 150 } 151 152 // return single code as fallback 153 return locale.getLanguage(); 154 } 155 156 /** 157 * Replies the locale code used by Java for a given locale. 158 * 159 * In most cases JOSM and Java uses the same codes, but for some exceptions this is needed. 160 * 161 * @param localeName the locale. Replies "en" if null. 162 * @return the Java code for the given locale 163 * @since 8232 164 */ 165 public static String getJavaLocaleCode(String localeName) { 166 if (localeName == null) return "en"; 167 switch (localeName) { 168 case "ca@valencia": 169 return "ca__valencia"; 170 case "sr@latin": 171 return "sr__latin"; 172 case "he": 173 return "iw_IL"; 174 case "id": 175 return "in"; 176 } 177 return localeName; 178 } 179 180 /** 181 * Replies the display string used by JOSM for a given locale. 182 * 183 * In most cases returns text replied by {@link Locale#getDisplayName()}, for some 184 * locales an override is used (i.e. when unsupported by Java). 185 * 186 * @param locale the locale. Replies "en" if null. 187 * @return the display string for the given locale 188 * @since 8232 189 */ 190 public static String getDisplayName(Locale locale) { 191 String currentCountry = Locale.getDefault().getCountry(); 192 String localeCountry = locale.getCountry(); 193 // Don't display locale country if country has been forced to current country at JOSM startup 194 if (currentCountry.equals(localeCountry) && !I18n.hasCode(getLanguageCodes(locale).get(0))) { 195 return new Locale(locale.getLanguage(), "", locale.getVariant()).getDisplayName(); 196 } 197 return locale.getDisplayName(); 198 } 199 200 /** 201 * Replies the locale used by Java for a given language code. 202 * 203 * Accepts JOSM and Java codes as input. 204 * 205 * @param localeName the locale code. 206 * @return the resulting locale 207 */ 208 public static Locale getLocale(String localeName) { 209 return getLocale(localeName, false); 210 } 211 212 /** 213 * Replies the locale used by Java for a given language code. 214 * 215 * Accepts JOSM, Java and POSIX codes as input. 216 * 217 * @param localeName the locale code. 218 * @param useDefaultCountry if {@code true}, the current locale country will be used if no country is specified 219 * @return the resulting locale 220 * @since 15547 221 */ 222 public static Locale getLocale(String localeName, boolean useDefaultCountry) { 223 final int encoding = localeName.indexOf('.'); 224 if (encoding > 0) { 225 localeName = localeName.substring(0, encoding); 226 } 227 int country = localeName.indexOf('_'); 228 int variant = localeName.indexOf('@'); 229 if (variant < 0 && country >= 0) 230 variant = localeName.indexOf('_', country+1); 231 Locale l; 232 if (variant > 0 && country > 0) { 233 l = new Locale(localeName.substring(0, country), localeName.substring(country+1, variant), localeName.substring(variant + 1)); 234 } else if (variant > 0) { 235 l = new Locale(localeName.substring(0, variant), "", localeName.substring(variant + 1)); 236 } else if (country > 0) { 237 l = new Locale(localeName.substring(0, country), localeName.substring(country + 1)); 238 } else { 239 l = new Locale(localeName, useDefaultCountry ? Locale.getDefault().getCountry() : ""); 240 } 241 return l; 242 } 243 244 /** 245 * Check if a new language is better than a previous existing. Can be used in classes where 246 * multiple user supplied language marked strings appear and the best one is searched. Following 247 * priorities: current language, english, any other 248 * 249 * @param oldLanguage the language code of the existing string 250 * @param newLanguage the language code of the new string 251 * @return true if new one is better 252 * @since 8091 253 */ 254 public static boolean isBetterLanguage(String oldLanguage, String newLanguage) { 255 if (oldLanguage == null) 256 return true; 257 String want = getJOSMLocaleCode(); 258 return want.equals(newLanguage) || (!want.equals(oldLanguage) && newLanguage.startsWith("en")); 259 } 260 261 /** 262 * Replies the language prefix for use in XML elements (with a dot appended). 263 * 264 * @return the XML language prefix 265 * @see #getJOSMLocaleCode() 266 */ 267 public static String getLanguageCodeXML() { 268 String code = getJOSMLocaleCode(); 269 code = code.replace('@', '-'); 270 return code+'.'; 271 } 272 273 /** 274 * Replies the language prefix for use in manifests (with an underscore appended). 275 * 276 * @return the manifest language prefix 277 * @see #getJOSMLocaleCode() 278 */ 279 public static String getLanguageCodeManifest() { 280 String code = getJOSMLocaleCode(); 281 code = code.replace('@', '-'); 282 return code+'_'; 283 } 284 285 /** 286 * Replies a list of language codes for local names. Prefixes range from very specific 287 * to more generic. 288 * <ul> 289 * <li>lang_COUNTRY@variant of the current locale</li> 290 * <li>lang@variant of the current locale</li> 291 * <li>lang_COUNTRY of the current locale</li> 292 * <li>lang of the current locale</li> 293 * </ul> 294 * 295 * @param l the locale to use, <code>null</code> for default locale 296 * @return list of codes 297 * @since 8283 298 */ 299 public static List<String> getLanguageCodes(Locale l) { 300 List<String> list = new ArrayList<>(4); 301 if (l == null) 302 l = Locale.getDefault(); 303 String lang = l.getLanguage(); 304 String c = l.getCountry(); 305 String v = l.getVariant(); 306 if (c.isEmpty()) 307 c = null; 308 if (!Utils.isEmpty(v)) { 309 if (c != null) 310 list.add(lang+'_'+c+'@'+v); 311 list.add(lang+'@'+v); 312 } 313 if (c != null) 314 list.add(lang+'_'+c); 315 list.add(lang); 316 return list; 317 } 318}