001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import java.util.Collection; 005import java.util.LinkedList; 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 return locale.getDisplayName(); 182 } 183 184 /** 185 * Replies the locale used by Java for a given language code. 186 * 187 * Accepts JOSM and Java codes as input. 188 * 189 * @param localeName the locale code. 190 * @return the resulting locale 191 */ 192 public static Locale getLocale(String localeName) { 193 int country = localeName.indexOf('_'); 194 int variant = localeName.indexOf('@'); 195 if (variant < 0 && country >= 0) 196 variant = localeName.indexOf('_', country+1); 197 Locale l; 198 if (variant > 0 && country > 0) { 199 l = new Locale(localeName.substring(0, country), localeName.substring(country+1, variant), localeName.substring(variant + 1)); 200 } else if (variant > 0) { 201 l = new Locale(localeName.substring(0, variant), "", localeName.substring(variant + 1)); 202 } else if (country > 0) { 203 l = new Locale(localeName.substring(0, country), localeName.substring(country + 1)); 204 } else { 205 l = new Locale(localeName); 206 } 207 return l; 208 } 209 210 /** 211 * Check if a new language is better than a previous existing. Can be used in classes where 212 * multiple user supplied language marked strings appear and the best one is searched. Following 213 * priorities: current language, english, any other 214 * 215 * @param oldLanguage the language code of the existing string 216 * @param newLanguage the language code of the new string 217 * @return true if new one is better 218 * @since 8091 219 */ 220 public static boolean isBetterLanguage(String oldLanguage, String newLanguage) { 221 if (oldLanguage == null) 222 return true; 223 String want = getJOSMLocaleCode(); 224 return want.equals(newLanguage) || (!want.equals(oldLanguage) && newLanguage.startsWith("en")); 225 } 226 227 /** 228 * Replies the language prefix for use in XML elements (with a dot appended). 229 * 230 * @return the XML language prefix 231 * @see #getJOSMLocaleCode() 232 */ 233 public static String getLanguageCodeXML() { 234 String code = getJOSMLocaleCode(); 235 code = code.replace('@', '-'); 236 return code+'.'; 237 } 238 239 /** 240 * Replies the language prefix for use in manifests (with an underscore appended). 241 * 242 * @return the manifest language prefix 243 * @see #getJOSMLocaleCode() 244 */ 245 public static String getLanguageCodeManifest() { 246 String code = getJOSMLocaleCode(); 247 code = code.replace('@', '-'); 248 return code+'_'; 249 } 250 251 /** 252 * Replies a list of language codes for local names. Prefixes range from very specific 253 * to more generic. 254 * <ul> 255 * <li>lang_COUNTRY@variant of the current locale</li> 256 * <li>lang@variant of the current locale</li> 257 * <li>lang_COUNTRY of the current locale</li> 258 * <li>lang of the current locale</li> 259 * </ul> 260 * 261 * @param l the locale to use, <code>null</code> for default locale 262 * @return list of codes 263 * @since 8283 264 */ 265 public static Collection<String> getLanguageCodes(Locale l) { 266 Collection<String> list = new LinkedList<>(); 267 if (l == null) 268 l = Locale.getDefault(); 269 String lang = l.getLanguage(); 270 String c = l.getCountry(); 271 String v = l.getVariant(); 272 if (c.isEmpty()) 273 c = null; 274 if (v != null && !v.isEmpty()) { 275 if (c != null) 276 list.add(lang+'_'+c+'@'+v); 277 list.add(lang+'@'+v); 278 } 279 if (c != null) 280 list.add(lang+'_'+c); 281 list.add(lang); 282 return list; 283 } 284}