diff --git a/app/build.gradle b/app/build.gradle
index f9b2e9bd9..f3159f741 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -97,6 +97,10 @@ android {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
+ androidResources {
+ generateLocaleConfig = true
+ }
+
buildFeatures {
viewBinding true
buildConfig true
diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java
index c4937431f..f74e8b7a7 100644
--- a/app/src/main/java/org/schabi/newpipe/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -190,6 +190,8 @@ public class MainActivity extends AppCompatActivity {
&& ReleaseVersionUtil.INSTANCE.isReleaseApk()) {
UpdateSettingsFragment.askForConsentToUpdateChecks(this);
}
+
+ Localization.migrateAppLanguageSettingIfNecessary(getApplicationContext());
}
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
index ec2bed67a..a8129f680 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
@@ -1,10 +1,15 @@
package org.schabi.newpipe.settings;
import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
+import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;
+import androidx.appcompat.app.AppCompatDelegate;
import androidx.preference.Preference;
import org.schabi.newpipe.DownloaderImpl;
@@ -17,12 +22,11 @@ import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.image.PreferredImageQuality;
import java.io.IOException;
+import java.util.Locale;
public class ContentSettingsFragment extends BasePreferenceFragment {
private String youtubeRestrictedModeEnabledKey;
- private Localization initialSelectedLocalization;
- private ContentCountry initialSelectedContentCountry;
private String initialLanguage;
@Override
@@ -31,12 +35,28 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
addPreferencesFromResourceRegistry();
- initialSelectedLocalization = org.schabi.newpipe.util.Localization
- .getPreferredLocalization(requireContext());
- initialSelectedContentCountry = org.schabi.newpipe.util.Localization
- .getPreferredContentCountry(requireContext());
initialLanguage = defaultPreferences.getString(getString(R.string.app_language_key), "en");
+ if (Build.VERSION.SDK_INT >= 33) {
+ requirePreference(R.string.app_language_key).setVisible(false);
+ final Preference newAppLanguagePref =
+ requirePreference(R.string.app_language_android_13_and_up_key);
+ newAppLanguagePref.setSummaryProvider(preference -> {
+ final Locale customLocale = AppCompatDelegate.getApplicationLocales().get(0);
+ if (customLocale != null) {
+ return customLocale.getDisplayName();
+ }
+ return getString(R.string.systems_language);
+ });
+ newAppLanguagePref.setOnPreferenceClickListener(preference -> {
+ final Intent intent = new Intent(Settings.ACTION_APP_LOCALE_SETTINGS)
+ .setData(Uri.fromParts("package", requireContext().getPackageName(), null));
+ startActivity(intent);
+ return true;
+ });
+ newAppLanguagePref.setVisible(true);
+ }
+
final Preference imageQualityPreference = requirePreference(R.string.image_quality_key);
imageQualityPreference.setOnPreferenceChangeListener(
(preference, newValue) -> {
@@ -72,19 +92,21 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
public void onDestroy() {
super.onDestroy();
- final Localization selectedLocalization = org.schabi.newpipe.util.Localization
- .getPreferredLocalization(requireContext());
- final ContentCountry selectedContentCountry = org.schabi.newpipe.util.Localization
- .getPreferredContentCountry(requireContext());
final String selectedLanguage =
defaultPreferences.getString(getString(R.string.app_language_key), "en");
- if (!selectedLocalization.equals(initialSelectedLocalization)
- || !selectedContentCountry.equals(initialSelectedContentCountry)
- || !selectedLanguage.equals(initialLanguage)) {
- Toast.makeText(requireContext(), R.string.localization_changes_requires_app_restart,
- Toast.LENGTH_LONG).show();
-
+ if (!selectedLanguage.equals(initialLanguage)) {
+ if (Build.VERSION.SDK_INT < 33) {
+ Toast.makeText(
+ requireContext(),
+ R.string.localization_changes_requires_app_restart,
+ Toast.LENGTH_LONG
+ ).show();
+ }
+ final Localization selectedLocalization = org.schabi.newpipe.util.Localization
+ .getPreferredLocalization(requireContext());
+ final ContentCountry selectedContentCountry = org.schabi.newpipe.util.Localization
+ .getPreferredContentCountry(requireContext());
NewPipe.setupLocalization(selectedLocalization, selectedContentCountry);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java
index 6830e390b..2146cf8bc 100644
--- a/app/src/main/java/org/schabi/newpipe/util/Localization.java
+++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java
@@ -12,12 +12,15 @@ import android.os.Build;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.DisplayMetrics;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.PluralsRes;
import androidx.annotation.StringRes;
+import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.math.MathUtils;
+import androidx.core.os.LocaleListCompat;
import androidx.preference.PreferenceManager;
import org.ocpsoft.prettytime.PrettyTime;
@@ -39,6 +42,7 @@ import java.time.format.FormatStyle;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
import java.util.stream.Collectors;
@@ -63,6 +67,7 @@ import java.util.stream.Collectors;
*/
public final class Localization {
+ private static final String TAG = Localization.class.toString();
public static final String DOT_SEPARATOR = " • ";
private static PrettyTime prettyTime;
@@ -101,6 +106,10 @@ public final class Localization {
}
public static Locale getAppLocale(@NonNull final Context context) {
+ if (Build.VERSION.SDK_INT >= 33) {
+ final Locale customLocale = AppCompatDelegate.getApplicationLocales().get(0);
+ return Objects.requireNonNullElseGet(customLocale, Locale::getDefault);
+ }
return getLocaleFromPrefs(context, R.string.app_language_key);
}
@@ -427,4 +436,32 @@ public final class Localization {
final int safeCount = (int) MathUtils.clamp(count, Integer.MIN_VALUE, Integer.MAX_VALUE);
return context.getResources().getQuantityString(pluralId, safeCount, formattedCount);
}
+
+ public static void migrateAppLanguageSettingIfNecessary(@NonNull final Context context) {
+ // Starting with pull request #12093, NewPipe on Android 13+ exclusively uses Android's
+ // public per-app language APIs to read and set the UI language for NewPipe.
+ // If running on Android 13+, the following code will migrate any existing custom
+ // app language in SharedPreferences to use the public per-app language APIs instead.
+ if (Build.VERSION.SDK_INT >= 33) {
+ final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+ final String appLanguageKey = context.getString(R.string.app_language_key);
+ final String appLanguageValue = sp.getString(appLanguageKey, null);
+ if (appLanguageValue != null) {
+ sp.edit().remove(appLanguageKey).apply();
+ final String appLanguageDefaultValue =
+ context.getString(R.string.default_localization_key);
+ if (!appLanguageValue.equals(appLanguageDefaultValue)) {
+ try {
+ AppCompatDelegate.setApplicationLocales(
+ LocaleListCompat.forLanguageTags(appLanguageValue)
+ );
+ } catch (final RuntimeException e) {
+ Log.e(TAG, "Failed to migrate previous custom app language "
+ + "setting to public per-app language APIs"
+ );
+ }
+ }
+ }
+ }
+ }
}
diff --git a/app/src/main/res/resources.properties b/app/src/main/res/resources.properties
new file mode 100644
index 000000000..467b3efec
--- /dev/null
+++ b/app/src/main/res/resources.properties
@@ -0,0 +1 @@
+unqualifiedResLocale=en-US
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index fb68a464d..61125c47f 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -353,6 +353,7 @@
playback_skip_silence_key
app_language_key
+ app_language_android_13_and_up_key
feed_update_threshold_key
300
diff --git a/app/src/main/res/xml/content_settings.xml b/app/src/main/res/xml/content_settings.xml
index 2cdc6c545..f17783a22 100644
--- a/app/src/main/res/xml/content_settings.xml
+++ b/app/src/main/res/xml/content_settings.xml
@@ -13,6 +13,13 @@
app:iconSpaceReserved="false"
app:useSimpleSummaryProvider="true" />
+
+