commit 8e988ae55dddbc4019bb498c39b20080183b10ac
parent d152432109fd54238ea30435a1182a45c5e88a6e
Author: Olivia Hall <ohall@mozilla.com>
Date: Mon, 10 Nov 2025 14:44:57 +0000
Bug 1998284 - Requested locales on Android 14+ need normalized r=m_kato,geckoview-reviewers
Language tags on Android 14 and greater can have extended formatting cues.
This normalizes the tag the same way as Accept-Languages.
Differential Revision: https://phabricator.services.mozilla.com/D271823
Diffstat:
3 files changed, 40 insertions(+), 24 deletions(-)
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/LocaleUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/LocaleUtils.java
@@ -10,9 +10,17 @@ import android.text.TextUtils;
import java.util.Locale;
public class LocaleUtils {
- // Locale.getLanguage() may return legacy language code until Java 17
- // https://developer.android.com/reference/java/util/Locale#legacy_language_codes
- public static String getLanguageTagForAcceptLanguage(final Locale locale) {
+ /**
+ * Function normalizes BCP-47 language tags to use non-legacy language codes and only return
+ * 'language-region' style codes for use with Accept-Language and Requested Locales.
+ *
+ * <p>Locale.getLanguage() may return legacy language code until Java 17
+ * https://developer.android.com/reference/java/util/Locale#legacy_language_codes
+ *
+ * @param locale The BCP-47 locale to normalize. e.g., iw-IL, zn-Hans-CN, en-US-u-mu-celsius,
+ * @return A normalized BCP-47 language code with only language-region, e.g., he-IL, zn-CN, en-US.
+ */
+ public static String getLanguageRegionLocale(final Locale locale) {
String language = locale.getLanguage();
if (language.equals("in")) {
language = "id";
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
@@ -1475,7 +1475,18 @@ public final class GeckoRuntimeSettings extends RuntimeSettings {
private void commitLocales() {
final GeckoBundle data = new GeckoBundle(1);
- data.putStringArray("requestedLocales", mRequestedLocales);
+ if (mRequestedLocales != null) {
+ // Requested locales should be in language or language-region format
+ final String[] normalizedRequestedLocales =
+ Arrays.stream(mRequestedLocales)
+ .map(Locale::forLanguageTag)
+ .map(LocaleUtils::getLanguageRegionLocale)
+ .toArray(String[]::new);
+ data.putStringArray("requestedLocales", normalizedRequestedLocales);
+ } else {
+ data.putStringArray("requestedLocales", (String[]) null);
+ }
+
data.putString("acceptLanguages", computeAcceptLanguages());
EventDispatcher.getInstance().dispatch("GeckoView:SetLocale", data);
}
@@ -1486,7 +1497,10 @@ public final class GeckoRuntimeSettings extends RuntimeSettings {
// Explicitly-set app prefs come first:
if (mRequestedLocales != null) {
for (final String locale : mRequestedLocales) {
- locales.put(locale.toLowerCase(Locale.ROOT), locale);
+ // Requested locales should be in language or language-region format
+ final String normalizedLocale =
+ LocaleUtils.getLanguageRegionLocale(Locale.forLanguageTag(locale));
+ locales.put(normalizedLocale.toLowerCase(Locale.ROOT), normalizedLocale);
}
}
// OS prefs come second:
@@ -1505,7 +1519,7 @@ public final class GeckoRuntimeSettings extends RuntimeSettings {
final String[] locales = new String[localeList.size()];
for (int i = 0; i < localeList.size(); i++) {
// accept-language should be language or language-region format.
- locales[i] = LocaleUtils.getLanguageTagForAcceptLanguage(localeList.get(i));
+ locales[i] = LocaleUtils.getLanguageRegionLocale(localeList.get(i));
}
return locales;
}
diff --git a/mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/LocaleUtilsTest.java b/mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/LocaleUtilsTest.java
@@ -16,32 +16,26 @@ import org.robolectric.RobolectricTestRunner;
public class LocaleUtilsTest {
@Test
public void languageTagForAcceptLanguage() {
- assertEquals(
- LocaleUtils.getLanguageTagForAcceptLanguage(Locale.forLanguageTag("zn-Hans-CN")), "zn-CN");
- assertEquals(
- LocaleUtils.getLanguageTagForAcceptLanguage(Locale.forLanguageTag("zn-Hant-TW")), "zn-TW");
+ assertEquals(LocaleUtils.getLanguageRegionLocale(Locale.forLanguageTag("zn-Hans-CN")), "zn-CN");
+ assertEquals(LocaleUtils.getLanguageRegionLocale(Locale.forLanguageTag("zn-Hant-TW")), "zn-TW");
// If builder is Java 17+, Locale.getLanguage doesn't repelace with old language code.
// But we should keep this to make things understandable.
- assertEquals(
- LocaleUtils.getLanguageTagForAcceptLanguage(Locale.forLanguageTag("id-ID")), "id-ID");
- assertEquals(
- LocaleUtils.getLanguageTagForAcceptLanguage(Locale.forLanguageTag("in-ID")), "id-ID");
+ assertEquals(LocaleUtils.getLanguageRegionLocale(Locale.forLanguageTag("id-ID")), "id-ID");
+ assertEquals(LocaleUtils.getLanguageRegionLocale(Locale.forLanguageTag("in-ID")), "id-ID");
- assertEquals(
- LocaleUtils.getLanguageTagForAcceptLanguage(Locale.forLanguageTag("yi-US")), "yi-US");
- assertEquals(
- LocaleUtils.getLanguageTagForAcceptLanguage(Locale.forLanguageTag("ji-US")), "yi-US");
+ assertEquals(LocaleUtils.getLanguageRegionLocale(Locale.forLanguageTag("yi-US")), "yi-US");
+ assertEquals(LocaleUtils.getLanguageRegionLocale(Locale.forLanguageTag("ji-US")), "yi-US");
- assertEquals(
- LocaleUtils.getLanguageTagForAcceptLanguage(Locale.forLanguageTag("he-IL")), "he-IL");
- assertEquals(
- LocaleUtils.getLanguageTagForAcceptLanguage(Locale.forLanguageTag("iw-IL")), "he-IL");
+ assertEquals(LocaleUtils.getLanguageRegionLocale(Locale.forLanguageTag("he-IL")), "he-IL");
+ assertEquals(LocaleUtils.getLanguageRegionLocale(Locale.forLanguageTag("iw-IL")), "he-IL");
// Android 14 may add extension (Bug 1873578)
assertEquals(
- LocaleUtils.getLanguageTagForAcceptLanguage(
- Locale.forLanguageTag("en-US-u-fw-mon-mu-celsius")),
+ LocaleUtils.getLanguageRegionLocale(Locale.forLanguageTag("en-US-u-fw-mon-mu-celsius")),
"en-US");
+
+ assertEquals(
+ LocaleUtils.getLanguageRegionLocale(Locale.forLanguageTag("en-US-u-mu-celsius")), "en-US");
}
}