Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
LocalizationPlugin.java
Go to the documentation of this file.
1// Copyright 2013 The Flutter 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
5package io.flutter.plugin.localization;
6
7import static io.flutter.Build.API_LEVELS;
8
9import android.annotation.SuppressLint;
10import android.content.Context;
11import android.content.res.Configuration;
12import android.os.Build;
13import android.os.LocaleList;
14import androidx.annotation.NonNull;
15import androidx.annotation.Nullable;
16import androidx.annotation.VisibleForTesting;
17import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
18import java.util.ArrayList;
19import java.util.List;
20import java.util.Locale;
21
22/** Android implementation of the localization plugin. */
23public class LocalizationPlugin {
24 @NonNull private final LocalizationChannel localizationChannel;
25 @NonNull private final Context context;
26
27 @SuppressLint({
28 "AppBundleLocaleChanges",
29 "DiscouragedApi"
30 }) // This is optionally turned on by apps.
31 @VisibleForTesting
32 final LocalizationChannel.LocalizationMessageHandler localizationMessageHandler =
33 new LocalizationChannel.LocalizationMessageHandler() {
34 @Override
35 public String getStringResource(@NonNull String key, @Nullable String localeString) {
36 Context localContext = context;
37 String stringToReturn = null;
38 Locale savedLocale = null;
39
40 if (localeString != null) {
41 Locale locale = localeFromString(localeString);
42
43 Configuration config = new Configuration(context.getResources().getConfiguration());
44 config.setLocale(locale);
45 localContext = context.createConfigurationContext(config);
46 }
47
48 String packageName = context.getPackageName();
49 int resId = localContext.getResources().getIdentifier(key, "string", packageName);
50 if (resId != 0) {
51 // 0 means the resource is not found.
52 stringToReturn = localContext.getResources().getString(resId);
53 }
54
55 return stringToReturn;
56 }
57 };
58
60 @NonNull Context context, @NonNull LocalizationChannel localizationChannel) {
61
62 this.context = context;
63 this.localizationChannel = localizationChannel;
64 this.localizationChannel.setLocalizationMessageHandler(localizationMessageHandler);
65 }
66
67 /**
68 * Computes the {@link Locale} in supportedLocales that best matches the user's preferred locales.
69 *
70 * <p>FlutterEngine must be non-null when this method is invoked.
71 */
72 @SuppressWarnings("deprecation")
73 @Nullable
74 public Locale resolveNativeLocale(@Nullable List<Locale> supportedLocales) {
75 if (supportedLocales == null || supportedLocales.isEmpty()) {
76 return null;
77 }
78
79 // Android improved the localization resolution algorithms after API 24 (7.0, Nougat).
80 // See https://developer.android.com/guide/topics/resources/multilingual-support
81 //
82 // LanguageRange and Locale.lookup was added in API 26 and is the preferred way to
83 // select a locale. Pre-API 26, we implement a manual locale resolution.
84 if (Build.VERSION.SDK_INT >= API_LEVELS.API_26) {
85 // Modern locale resolution using LanguageRange
86 // https://developer.android.com/guide/topics/resources/multilingual-support#postN
87 List<Locale.LanguageRange> languageRanges = new ArrayList<>();
88 LocaleList localeList = context.getResources().getConfiguration().getLocales();
89 int localeCount = localeList.size();
90 for (int index = 0; index < localeCount; ++index) {
91 Locale locale = localeList.get(index);
92 // Convert locale string into language range format.
93 String fullRange = locale.getLanguage();
94 if (!locale.getScript().isEmpty()) {
95 fullRange += "-" + locale.getScript();
96 }
97 if (!locale.getCountry().isEmpty()) {
98 fullRange += "-" + locale.getCountry();
99 }
100 languageRanges.add(new Locale.LanguageRange(fullRange));
101 languageRanges.add(new Locale.LanguageRange(locale.getLanguage()));
102 languageRanges.add(new Locale.LanguageRange(locale.getLanguage() + "-*"));
103 }
104 Locale platformResolvedLocale = Locale.lookup(languageRanges, supportedLocales);
105 if (platformResolvedLocale != null) {
106 return platformResolvedLocale;
107 }
108 return supportedLocales.get(0);
109 } else if (Build.VERSION.SDK_INT >= API_LEVELS.API_24) {
110 // Modern locale resolution without languageRange
111 // https://developer.android.com/guide/topics/resources/multilingual-support#postN
112 LocaleList localeList = context.getResources().getConfiguration().getLocales();
113 for (int index = 0; index < localeList.size(); ++index) {
114 Locale preferredLocale = localeList.get(index);
115 // Look for exact match.
116 for (Locale locale : supportedLocales) {
117 if (preferredLocale.equals(locale)) {
118 return locale;
119 }
120 }
121 // Look for exact language only match.
122 for (Locale locale : supportedLocales) {
123 if (preferredLocale.getLanguage().equals(locale.toLanguageTag())) {
124 return locale;
125 }
126 }
127 // Look for any locale with matching language.
128 for (Locale locale : supportedLocales) {
129 if (preferredLocale.getLanguage().equals(locale.getLanguage())) {
130 return locale;
131 }
132 }
133 }
134 return supportedLocales.get(0);
135 }
136
137 // Legacy locale resolution
138 // https://developer.android.com/guide/topics/resources/multilingual-support#preN
139 Locale preferredLocale = context.getResources().getConfiguration().locale;
140 if (preferredLocale != null) {
141 // Look for exact match.
142 for (Locale locale : supportedLocales) {
143 if (preferredLocale.equals(locale)) {
144 return locale;
145 }
146 }
147 // Look for exact language only match.
148 for (Locale locale : supportedLocales) {
149 if (preferredLocale.getLanguage().equals(locale.toString())) {
150 return locale;
151 }
152 }
153 }
154 return supportedLocales.get(0);
155 }
156
157 /**
158 * Send the current {@link Locale} configuration to Flutter.
159 *
160 * <p>FlutterEngine must be non-null when this method is invoked.
161 */
162 @SuppressWarnings("deprecation")
163 public void sendLocalesToFlutter(@NonNull Configuration config) {
164 List<Locale> locales = new ArrayList<>();
165 if (Build.VERSION.SDK_INT >= API_LEVELS.API_24) {
166 LocaleList localeList = config.getLocales();
167 int localeCount = localeList.size();
168 for (int index = 0; index < localeCount; ++index) {
169 Locale locale = localeList.get(index);
170 locales.add(locale);
171 }
172 } else {
173 locales.add(config.locale);
174 }
175
176 localizationChannel.sendLocales(locales);
177 }
178
179 /**
180 * Computes the {@link Locale} from the provided {@code String} with format
181 * language[-script][-region][-...], where script is an alphabet string of length 4, and region is
182 * either an alphabet string of length 2 or a digit string of length 3.
183 */
184 @NonNull
185 public static Locale localeFromString(@NonNull String localeString) {
186 // Normalize the locale string, replace all underscores with hyphens.
187 localeString = localeString.replace('_', '-');
188
189 // Pre-API 21, we fall back to manually parsing the locale tag.
190 String parts[] = localeString.split("-", -1);
191
192 // Assume the first part is always the language code.
193 String languageCode = parts[0];
194 String scriptCode = "";
195 String countryCode = "";
196 int index = 1;
197 if (parts.length > index && parts[index].length() == 4) {
198 scriptCode = parts[index];
199 index++;
200 }
201 if (parts.length > index && parts[index].length() >= 2 && parts[index].length() <= 3) {
202 countryCode = parts[index];
203 index++;
204 }
205 // Ignore the rest of the locale for this purpose.
206 return new Locale(languageCode, countryCode, scriptCode);
207 }
208}
void add(sk_sp< SkIDChangeListener > listener) SK_EXCLUDES(fMutex)
Locale resolveNativeLocale(@Nullable List< Locale > supportedLocales)
final LocalizationChannel.LocalizationMessageHandler localizationMessageHandler
void sendLocalesToFlutter(@NonNull Configuration config)
static Locale localeFromString(@NonNull String localeString)
LocalizationPlugin( @NonNull Context context, @NonNull LocalizationChannel localizationChannel)