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