-
-
Notifications
You must be signed in to change notification settings - Fork 3.3k
[Android 13+] Support per-app language preferences #12093
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b888dc7
87693a2
3532ac9
da106e2
980a35a
70416e7
35abb99
c7bf498
7f10312
205466c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.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 void onCreatePreferences(final Bundle savedInstanceState, final String ro | |
|
||
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); | ||
Comment on lines
+52
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a very edge case, but if the user has disabled settings or if some ROMs are broken (hello weird Android TV devices), this would crash the app due to no app being able to handle the intent. |
||
return true; | ||
}); | ||
newAppLanguagePref.setVisible(true); | ||
} | ||
|
||
final Preference imageQualityPreference = requirePreference(R.string.image_quality_key); | ||
imageQualityPreference.setOnPreferenceChangeListener( | ||
(preference, newValue) -> { | ||
|
@@ -72,19 +92,21 @@ public boolean onPreferenceTreeClick(final Preference preference) { | |
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); | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,12 +12,15 @@ | |
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.util.Arrays; | ||
import java.util.List; | ||
import java.util.Locale; | ||
import java.util.Objects; | ||
import java.util.stream.Collectors; | ||
|
||
|
||
|
@@ -63,6 +67,7 @@ | |
*/ | ||
|
||
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 static Locale getPreferredLocale(@NonNull final Context context) { | |
} | ||
|
||
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 @@ private static String getQuantity(@NonNull final Context context, | |
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" | ||
); | ||
Comment on lines
+459
to
+461
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't the error be logged? |
||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
unqualifiedResLocale=en-US |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,13 @@ | |
app:iconSpaceReserved="false" | ||
app:useSimpleSummaryProvider="true" /> | ||
|
||
<Preference | ||
android:key="@string/app_language_android_13_and_up_key" | ||
android:title="@string/app_language_title" | ||
app:isPreferenceVisible="false" | ||
app:singleLineTitle="false" | ||
app:iconSpaceReserved="false" /> | ||
Comment on lines
+16
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a separate |
||
|
||
<ListPreference | ||
android:defaultValue="@string/default_localization_key" | ||
android:entries="@array/language_names" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we don't pay attention to new languages being added from Weblate, this might lead to half-translated being proposed to the user. Currently the list of languages is manually kept in
settings_keys.xml
, so when new languages are added through Weblate they don't automatically appear in the app menu. However, I'm fine with switching to the new behavior, as it would avoid work to keep the language list up to date. Is it possible to access the locale config from within the app, so we can delete the list of languages insettings_keys.xml
and use the autogenerated locale config even for the pre-Android-13 language picker?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found LocaleManager#getOverrideLocaleConfig but it sounds like that method is only to get a
LocaleConfig
that's already been set withsetOverrideLocaleConfig()
. As far as I can tell, we would have to manually generate aLocaleConfig
instead of using the auto-generated one if we wanted to use it for this.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could find the data under
app/build/generated/res/localeConfig/debug/xml/_generated_res_locale_config.xml
after running the build, with this data inside:So in theory the data is there, but in practice I think we better not access it by manually opening the xml since it's not officially supported. So yeah let's keep the previous behavior.