| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.base; |
| |
| import android.content.Context; |
| import android.content.res.Configuration; |
| import android.os.Build; |
| import android.os.LocaleList; |
| import android.text.TextUtils; |
| |
| import androidx.annotation.RequiresApi; |
| import androidx.annotation.VisibleForTesting; |
| |
| import org.jni_zero.CalledByNative; |
| |
| import java.util.ArrayList; |
| import java.util.Locale; |
| |
| /** This class provides the locale related methods. */ |
| public class LocaleUtils { |
| /** Guards this class from being instantiated. */ |
| private LocaleUtils() {} |
| |
| /** |
| * Java keeps deprecated language codes for Hebrew, Yiddish and Indonesian but Chromium uses |
| * updated ones. Similarly, Android uses "tl" while Chromium uses "fil" for Tagalog/Filipino. |
| * The Translate settings use "gom", but Chrome uses "kok". Apply a mapping here. See |
| * http://developer.android.com/reference/java/util/Locale.html |
| * @return a updated language code for Chromium with given language string. |
| */ |
| public static String getUpdatedLanguageForChromium(String language) { |
| // IMPORTANT: If adding a new Chrome UI language, update the mapping found in: |
| // build/android/gyp/util/resource_utils.py (Languages that are accept languages, but not |
| // Chrome Android UI languages do not need to be kept in sync). |
| switch (language) { |
| case "gom": |
| return "kok"; // Konkani |
| case "in": |
| return "id"; // Indonesian |
| case "iw": |
| return "he"; // Hebrew |
| case "ji": |
| return "yi"; // Yiddish |
| case "jw": |
| return "jv"; // Javanese |
| case "tl": |
| return "fil"; // Filipino |
| default: |
| return language; |
| } |
| } |
| |
| /** |
| * @return a locale with updated language codes for Chromium, with translated modern language |
| * codes used by Chromium. |
| */ |
| @VisibleForTesting |
| public static Locale getUpdatedLocaleForChromium(Locale locale) { |
| String language = locale.getLanguage(); |
| String languageForChrome = getUpdatedLanguageForChromium(language); |
| if (languageForChrome.equals(language)) { |
| return locale; |
| } |
| return new Locale.Builder().setLocale(locale).setLanguage(languageForChrome).build(); |
| } |
| |
| /** |
| * Android uses "tl" while Chromium uses "fil" for Tagalog/Filipino. |
| * So apply a mapping here. |
| * See http://developer.android.com/reference/java/util/Locale.html |
| * @return a updated language code for Android with given language string. |
| */ |
| public static String getUpdatedLanguageForAndroid(String language) { |
| // IMPORTANT: Keep in sync with the mapping found in: |
| // build/android/gyp/util/resource_utils.py |
| switch (language) { |
| case "und": |
| return ""; // Undefined |
| case "fil": |
| return "tl"; // Filipino |
| default: |
| return language; |
| } |
| } |
| |
| /** |
| * @return a locale with updated language codes for Android, from translated modern language |
| * codes used by Chromium. |
| */ |
| @VisibleForTesting |
| public static Locale getUpdatedLocaleForAndroid(Locale locale) { |
| String language = locale.getLanguage(); |
| String languageForAndroid = getUpdatedLanguageForAndroid(language); |
| if (languageForAndroid.equals(language)) { |
| return locale; |
| } |
| return new Locale.Builder().setLocale(locale).setLanguage(languageForAndroid).build(); |
| } |
| |
| /** |
| * This function creates a Locale object from xx-XX style string where xx is language code |
| * and XX is a country code. |
| * @return the locale that best represents the language tag. |
| */ |
| public static Locale forLanguageTag(String languageTag) { |
| Locale locale = Locale.forLanguageTag(languageTag); |
| return getUpdatedLocaleForAndroid(locale); |
| } |
| |
| /** |
| * Converts Locale object to the BCP 47 compliant string format. |
| * This works for API level lower than 24. |
| * |
| * Note that for Android M or before, we cannot use Locale.getLanguage() and |
| * Locale.toLanguageTag() for this purpose. Since Locale.getLanguage() returns deprecated |
| * language code even if the Locale object is constructed with updated language code. As for |
| * Locale.toLanguageTag(), it does a special conversion from deprecated language code to updated |
| * one, but it is only usable for Android N or after. |
| * @return a well-formed IETF BCP 47 language tag with language and country code that |
| * represents this locale. |
| */ |
| public static String toLanguageTag(Locale locale) { |
| String language = getUpdatedLanguageForChromium(locale.getLanguage()); |
| String country = locale.getCountry(); |
| if (language.equals("no") && country.equals("NO") && locale.getVariant().equals("NY")) { |
| return "nn-NO"; |
| } |
| return country.isEmpty() ? language : language + "-" + country; |
| } |
| |
| /** |
| * Converts LocaleList object to the comma separated BCP 47 compliant string format. |
| * |
| * @return a well-formed IETF BCP 47 language tag with language and country code that |
| * represents this locale list. |
| */ |
| @RequiresApi(Build.VERSION_CODES.N) |
| public static String toLanguageTags(LocaleList localeList) { |
| ArrayList<String> newLocaleList = new ArrayList<>(); |
| for (int i = 0; i < localeList.size(); i++) { |
| Locale locale = getUpdatedLocaleForChromium(localeList.get(i)); |
| newLocaleList.add(toLanguageTag(locale)); |
| } |
| return TextUtils.join(",", newLocaleList); |
| } |
| |
| /** |
| * Extracts the base language from a BCP 47 language tag. |
| * @param languageTag language tag of the form xx-XX or xx. |
| * @return the xx part of the language tag. |
| */ |
| public static String toBaseLanguage(String languageTag) { |
| int pos = languageTag.indexOf('-'); |
| if (pos < 0) { |
| return languageTag; |
| } |
| return languageTag.substring(0, pos); |
| } |
| |
| /** |
| * @param first A BCP 47 formated language tag. |
| * @param second A BCP 47 formated language tag. |
| * @return True if the base language (e.g. "en" for "en-AU") is the same for each tag. |
| */ |
| public static boolean isBaseLanguageEqual(String first, String second) { |
| return TextUtils.equals(toBaseLanguage(first), toBaseLanguage(second)); |
| } |
| |
| /** |
| * @return a language tag string that represents the default locale. |
| * The language tag is well-formed IETF BCP 47 language tag with language and country |
| * code. |
| */ |
| @CalledByNative |
| public static String getDefaultLocaleString() { |
| return toLanguageTag(Locale.getDefault()); |
| } |
| |
| /** |
| * @return a comma separated language tags string that represents a default locale or locales. |
| * Each language tag is well-formed IETF BCP 47 language tag with language and country |
| * code. |
| */ |
| @CalledByNative |
| public static String getDefaultLocaleListString() { |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |
| return toLanguageTags(LocaleList.getDefault()); |
| } |
| return getDefaultLocaleString(); |
| } |
| |
| /** |
| * @return The default country code set during install. |
| */ |
| @CalledByNative |
| private static String getDefaultCountryCode() { |
| CommandLine commandLine = CommandLine.getInstance(); |
| return commandLine.hasSwitch(BaseSwitches.DEFAULT_COUNTRY_CODE_AT_INSTALL) |
| ? commandLine.getSwitchValue(BaseSwitches.DEFAULT_COUNTRY_CODE_AT_INSTALL) |
| : Locale.getDefault().getCountry(); |
| } |
| |
| /** |
| * Return the language tag of the first language in Configuration. |
| * @param config Configuration to get language for. |
| * @return The BCP 47 tag representation of the configuration's first locale. |
| * Configuration.locale is deprecated on N+. However, read only is equivalent to |
| * Configuration.getLocales()[0]. Change when minSdkVersion >= 24. |
| */ |
| @SuppressWarnings("deprecation") |
| public static String getConfigurationLanguage(Configuration config) { |
| Locale locale = config.locale; |
| return (locale != null) ? locale.toLanguageTag() : ""; |
| } |
| |
| /** |
| * Return the language tag of the first language in the configuration |
| * @param context Context to get language for. |
| * @return The BCP 47 tag representation of the context's first locale. |
| */ |
| public static String getContextLanguage(Context context) { |
| return getConfigurationLanguage(context.getResources().getConfiguration()); |
| } |
| |
| /** |
| * Prepend languageTag to the default locales on config. |
| * @param base The Context to use for the base configuration. |
| * @param config The Configuration to update. |
| * @param languageTag The language to prepend to default locales. |
| */ |
| public static void updateConfig(Context base, Configuration config, String languageTag) { |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |
| ApisN.setConfigLocales(base, config, languageTag); |
| } else { |
| config.setLocale(Locale.forLanguageTag(languageTag)); |
| } |
| } |
| |
| /** |
| * Updates the default Locale/LocaleList to those of config. |
| * @param config |
| */ |
| public static void setDefaultLocalesFromConfiguration(Configuration config) { |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |
| ApisN.setLocaleList(config); |
| } else { |
| Locale.setDefault(config.locale); |
| } |
| } |
| |
| /** Helper class for N only code that is not validated on pre-N devices. */ |
| @RequiresApi(Build.VERSION_CODES.N) |
| @VisibleForTesting |
| static class ApisN { |
| static void setConfigLocales(Context base, Configuration config, String language) { |
| LocaleList updatedLocales = |
| prependToLocaleList( |
| language, base.getResources().getConfiguration().getLocales()); |
| config.setLocales(updatedLocales); |
| } |
| |
| static void setLocaleList(Configuration config) { |
| LocaleList.setDefault(config.getLocales()); |
| } |
| |
| /** |
| * Create a new LocaleList with languageTag added to the front. |
| * If languageTag is already in the list the existing tag is moved to the front. |
| * @param languageTag String of language tag to prepend |
| * @param localeList LocaleList to prepend to. |
| * @return LocaleList |
| */ |
| static LocaleList prependToLocaleList(String languageTag, LocaleList localeList) { |
| String languageList = localeList.toLanguageTags(); |
| |
| // Remove the first instance of languageTag with associated comma if present. |
| // Pattern example: "(^|,)en-US$|en-US," |
| String pattern = String.format("(^|,)%1$s$|%1$s,", languageTag); |
| languageList = languageList.replaceFirst(pattern, ""); |
| |
| return LocaleList.forLanguageTags( |
| String.format("%1$s,%2$s", languageTag, languageList)); |
| } |
| } |
| } |