1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base; 6 7 import android.annotation.TargetApi; 8 import android.os.Build; 9 import android.os.LocaleList; 10 import android.text.TextUtils; 11 12 import org.chromium.base.annotations.CalledByNative; 13 14 import java.util.ArrayList; 15 import java.util.Locale; 16 17 /** 18 * This class provides the locale related methods. 19 */ 20 public class LocaleUtils { 21 /** 22 * Guards this class from being instantiated. 23 */ LocaleUtils()24 private LocaleUtils() { 25 } 26 27 /** 28 * Java keeps deprecated language codes for Hebrew, Yiddish and Indonesian but Chromium uses 29 * updated ones. Similarly, Android uses "tl" while Chromium uses "fil" for Tagalog/Filipino. 30 * So apply a mapping here. 31 * See http://developer.android.com/reference/java/util/Locale.html 32 * @return a updated language code for Chromium with given language string. 33 */ getUpdatedLanguageForChromium(String language)34 public static String getUpdatedLanguageForChromium(String language) { 35 // IMPORTANT: Keep in sync with the mapping found in: 36 // build/android/gyp/util/resource_utils.py 37 switch (language) { 38 case "iw": 39 return "he"; // Hebrew 40 case "ji": 41 return "yi"; // Yiddish 42 case "in": 43 return "id"; // Indonesian 44 case "tl": 45 return "fil"; // Filipino 46 default: 47 return language; 48 } 49 } 50 51 /** 52 * @return a locale with updated language codes for Chromium, with translated modern language 53 * codes used by Chromium. 54 */ 55 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 56 @VisibleForTesting getUpdatedLocaleForChromium(Locale locale)57 public static Locale getUpdatedLocaleForChromium(Locale locale) { 58 String language = locale.getLanguage(); 59 String languageForChrome = getUpdatedLanguageForChromium(language); 60 if (languageForChrome.equals(language)) { 61 return locale; 62 } 63 return new Locale.Builder().setLocale(locale).setLanguage(languageForChrome).build(); 64 } 65 66 /** 67 * Android uses "tl" while Chromium uses "fil" for Tagalog/Filipino. 68 * So apply a mapping here. 69 * See http://developer.android.com/reference/java/util/Locale.html 70 * @return a updated language code for Android with given language string. 71 */ getUpdatedLanguageForAndroid(String language)72 public static String getUpdatedLanguageForAndroid(String language) { 73 // IMPORTANT: Keep in sync with the mapping found in: 74 // build/android/gyp/util/resource_utils.py 75 switch (language) { 76 case "und": 77 return ""; // Undefined 78 case "fil": 79 return "tl"; // Filipino 80 default: 81 return language; 82 } 83 } 84 85 /** 86 * @return a locale with updated language codes for Android, from translated modern language 87 * codes used by Chromium. 88 */ 89 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 90 @VisibleForTesting getUpdatedLocaleForAndroid(Locale locale)91 public static Locale getUpdatedLocaleForAndroid(Locale locale) { 92 String language = locale.getLanguage(); 93 String languageForAndroid = getUpdatedLanguageForAndroid(language); 94 if (languageForAndroid.equals(language)) { 95 return locale; 96 } 97 return new Locale.Builder().setLocale(locale).setLanguage(languageForAndroid).build(); 98 } 99 100 /** 101 * This function creates a Locale object from xx-XX style string where xx is language code 102 * and XX is a country code. This works for API level lower than 21. 103 * @return the locale that best represents the language tag. 104 */ forLanguageTagCompat(String languageTag)105 public static Locale forLanguageTagCompat(String languageTag) { 106 String[] tag = languageTag.split("-"); 107 if (tag.length == 0) { 108 return new Locale(""); 109 } 110 String language = getUpdatedLanguageForAndroid(tag[0]); 111 if ((language.length() != 2 && language.length() != 3)) { 112 return new Locale(""); 113 } 114 if (tag.length == 1) { 115 return new Locale(language); 116 } 117 String country = tag[1]; 118 if (country.length() != 2 && country.length() != 3) { 119 return new Locale(language); 120 } 121 return new Locale(language, country); 122 } 123 124 /** 125 * This function creates a Locale object from xx-XX style string where xx is language code 126 * and XX is a country code. 127 * @return the locale that best represents the language tag. 128 */ forLanguageTag(String languageTag)129 public static Locale forLanguageTag(String languageTag) { 130 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 131 Locale locale = Locale.forLanguageTag(languageTag); 132 return getUpdatedLocaleForAndroid(locale); 133 } 134 return forLanguageTagCompat(languageTag); 135 } 136 137 /** 138 * Converts Locale object to the BCP 47 compliant string format. 139 * This works for API level lower than 24. 140 * 141 * Note that for Android M or before, we cannot use Locale.getLanguage() and 142 * Locale.toLanguageTag() for this purpose. Since Locale.getLanguage() returns deprecated 143 * language code even if the Locale object is constructed with updated language code. As for 144 * Locale.toLanguageTag(), it does a special conversion from deprecated language code to updated 145 * one, but it is only usable for Android N or after. 146 * @return a well-formed IETF BCP 47 language tag with language and country code that 147 * represents this locale. 148 */ toLanguageTag(Locale locale)149 public static String toLanguageTag(Locale locale) { 150 String language = getUpdatedLanguageForChromium(locale.getLanguage()); 151 String country = locale.getCountry(); 152 if (language.equals("no") && country.equals("NO") && locale.getVariant().equals("NY")) { 153 return "nn-NO"; 154 } 155 return country.isEmpty() ? language : language + "-" + country; 156 } 157 158 /** 159 * Converts LocaleList object to the comma separated BCP 47 compliant string format. 160 * 161 * @return a well-formed IETF BCP 47 language tag with language and country code that 162 * represents this locale list. 163 */ 164 @TargetApi(Build.VERSION_CODES.N) toLanguageTags(LocaleList localeList)165 public static String toLanguageTags(LocaleList localeList) { 166 ArrayList<String> newLocaleList = new ArrayList<>(); 167 for (int i = 0; i < localeList.size(); i++) { 168 Locale locale = getUpdatedLocaleForChromium(localeList.get(i)); 169 newLocaleList.add(toLanguageTag(locale)); 170 } 171 return TextUtils.join(",", newLocaleList); 172 } 173 174 /** 175 * @return a comma separated language tags string that represents a default locale. 176 * Each language tag is well-formed IETF BCP 47 language tag with language and country 177 * code. 178 */ 179 @CalledByNative getDefaultLocaleString()180 public static String getDefaultLocaleString() { 181 return toLanguageTag(Locale.getDefault()); 182 } 183 184 /** 185 * @return a comma separated language tags string that represents a default locale or locales. 186 * Each language tag is well-formed IETF BCP 47 language tag with language and country 187 * code. 188 */ getDefaultLocaleListString()189 public static String getDefaultLocaleListString() { 190 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 191 return toLanguageTags(LocaleList.getDefault()); 192 } 193 return getDefaultLocaleString(); 194 } 195 196 /** 197 * @return The default country code set during install. 198 */ 199 @CalledByNative getDefaultCountryCode()200 private static String getDefaultCountryCode() { 201 CommandLine commandLine = CommandLine.getInstance(); 202 return commandLine.hasSwitch(BaseSwitches.DEFAULT_COUNTRY_CODE_AT_INSTALL) 203 ? commandLine.getSwitchValue(BaseSwitches.DEFAULT_COUNTRY_CODE_AT_INSTALL) 204 : Locale.getDefault().getCountry(); 205 } 206 207 } 208