• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
LocaleUtils()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      */
getUpdatedLanguageForChromium(String language)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
getUpdatedLocaleForChromium(Locale locale)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      */
getUpdatedLanguageForAndroid(String language)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
getUpdatedLocaleForAndroid(Locale locale)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      */
forLanguageTagCompat(String languageTag)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      */
forLanguageTag(String languageTag)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      */
toLanguageTag(Locale locale)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)
toLanguageTags(LocaleList localeList)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
getDefaultLocaleString()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      */
getDefaultLocaleListString()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
getDefaultCountryCode()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