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