diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java index c7eb05699a..037744c7e3 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java @@ -40,6 +40,8 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import app.revanced.extension.shared.settings.AppLanguage; +import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BooleanSetting; import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference; @@ -360,7 +362,17 @@ public static Context getContext() { } public static void setContext(Context appContext) { + // Must initially set context as the language settings needs it. context = appContext; + + AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get(); + if (language != AppLanguage.DEFAULT) { + // Create a new context with the desired language. + Configuration config = appContext.getResources().getConfiguration(); + config.setLocale(language.getLocale()); + context = appContext.createConfigurationContext(config); + } + // In some apps like TikTok, the Setting classes can load in weird orders due to cyclic class dependencies. // Calling the regular printDebug method here can cause a Settings context null pointer exception, // even though the context is already set before the call. @@ -765,8 +777,8 @@ public static void setPreferenceTitlesToMultiLineIfNeeded(PreferenceGroup group) return; } - String deviceLanguage = Utils.getContext().getResources().getConfiguration().locale.getLanguage(); - if (deviceLanguage.equals("en")) { + String revancedLocale = Utils.getContext().getResources().getConfiguration().locale.getLanguage(); + if (revancedLocale.equals(Locale.ENGLISH.getLanguage())) { return; } @@ -774,8 +786,8 @@ public static void setPreferenceTitlesToMultiLineIfNeeded(PreferenceGroup group) Preference pref = group.getPreference(i); pref.setSingleLineTitle(false); - if (pref instanceof PreferenceGroup) { - setPreferenceTitlesToMultiLineIfNeeded((PreferenceGroup) pref); + if (pref instanceof PreferenceGroup subGroup) { + setPreferenceTitlesToMultiLineIfNeeded(subGroup); } } } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/AudioStreamLanguage.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/AppLanguage.java similarity index 79% rename from extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/AudioStreamLanguage.java rename to extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/AppLanguage.java index d94b0069f6..e6d029a2e4 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/AudioStreamLanguage.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/AppLanguage.java @@ -1,8 +1,8 @@ -package app.revanced.extension.shared.spoof; +package app.revanced.extension.shared.settings; import java.util.Locale; -public enum AudioStreamLanguage { +public enum AppLanguage { /** * The current app language. */ @@ -34,7 +34,7 @@ public enum AudioStreamLanguage { GL, GU, HI, - HE, // App uses obsolete 'IW' and 'HE' is modern ISO code. + HE, // App uses obsolete 'IW' and not the modern 'HE' ISO code. HR, HU, HY, @@ -87,7 +87,7 @@ public enum AudioStreamLanguage { private final String language; - AudioStreamLanguage() { + AppLanguage() { language = name().toLowerCase(Locale.US); } @@ -103,4 +103,12 @@ public String getLanguage() { return language; } + + public Locale getLocale() { + if (this == DEFAULT) { + return Locale.getDefault(); + } + + return Locale.forLanguageTag(language); + } } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java index 136d1d468f..8eeedc9a58 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java @@ -6,7 +6,6 @@ import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.AudioStreamLanguageOverrideAvailability; import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.SpoofiOSAvailability; -import app.revanced.extension.shared.spoof.AudioStreamLanguage; import app.revanced.extension.shared.spoof.ClientType; /** @@ -22,8 +21,10 @@ public class BaseSettings { public static final IntegerSetting CHECK_ENVIRONMENT_WARNINGS_ISSUED = new IntegerSetting("revanced_check_environment_warnings_issued", 0, true, false); + public static final EnumSetting REVANCED_LANGUAGE = new EnumSetting<>("revanced_language", AppLanguage.DEFAULT, true, "revanced_language_user_dialog_message"); + public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message"); - public static final EnumSetting SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AudioStreamLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability()); + public static final EnumSetting SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AppLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability()); public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS)); public static final BooleanSetting SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_video_streams_ios_force_avc", FALSE, true, "revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofiOSAvailability()); diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/PlayerRoutes.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/PlayerRoutes.java index 9974064e97..0f51009d8e 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/PlayerRoutes.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/PlayerRoutes.java @@ -10,7 +10,7 @@ import app.revanced.extension.shared.requests.Requester; import app.revanced.extension.shared.requests.Route; import app.revanced.extension.shared.settings.BaseSettings; -import app.revanced.extension.shared.spoof.AudioStreamLanguage; +import app.revanced.extension.shared.settings.AppLanguage; import app.revanced.extension.shared.spoof.ClientType; final class PlayerRoutes { @@ -42,9 +42,9 @@ static String createInnertubeBody(ClientType clientType) { // but if this is a fall over client it will set the language even though // the audio language is not selectable in the UI. ClientType userSelectedClient = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get(); - AudioStreamLanguage language = userSelectedClient == ClientType.ANDROID_VR_NO_AUTH + AppLanguage language = userSelectedClient == ClientType.ANDROID_VR_NO_AUTH ? BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get() - : AudioStreamLanguage.DEFAULT; + : AppLanguage.DEFAULT; JSONObject client = new JSONObject(); client.put("hl", language.getLanguage()); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ExitFullscreenPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ExitFullscreenPatch.java index 02d53fa696..972c2cd924 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ExitFullscreenPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ExitFullscreenPatch.java @@ -49,7 +49,10 @@ public static void endOfVideoReached() { Logger.printDebug(() -> "Fullscreen button is null, cannot click"); } else { Logger.printDebug(() -> "Clicking fullscreen button"); + final boolean soundEffectsEnabled = button.isSoundEffectsEnabled(); + button.setSoundEffectsEnabled(false); button.performClick(); + button.setSoundEffectsEnabled(soundEffectsEnabled); } }); } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/LicenseActivityHook.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/LicenseActivityHook.java index 1ca0f5c197..1812ca5c54 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/LicenseActivityHook.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/LicenseActivityHook.java @@ -2,11 +2,15 @@ import android.annotation.SuppressLint; import android.app.Activity; +import android.content.Context; import android.preference.PreferenceFragment; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.TextView; import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.Utils; +import app.revanced.extension.shared.settings.AppLanguage; +import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.youtube.ThemeHelper; import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment; import app.revanced.extension.youtube.settings.preference.ReturnYouTubeDislikePreferenceFragment; @@ -25,6 +29,19 @@ @SuppressWarnings("unused") public class LicenseActivityHook { + /** + * Injection point. + * Overrides the ReVanced settings language. + */ + public static Context getAttachBaseContext(Context original) { + AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get(); + if (language == AppLanguage.DEFAULT) { + return original; + } + + return Utils.getContext(); + } + /** * Injection point. *

diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java index 6511fc00ce..02804c8c90 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java @@ -25,6 +25,8 @@ import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Utils; +import app.revanced.extension.shared.settings.BaseSettings; +import app.revanced.extension.shared.settings.EnumSetting; import app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment; import app.revanced.extension.youtube.ThemeHelper; import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch; @@ -109,15 +111,20 @@ protected void initialize() { CustomPlaybackSpeedPatch.initializeListPreference(playbackPreference); } - preference = findPreference(Settings.SPOOF_VIDEO_STREAMS_LANGUAGE.key); - if (preference instanceof ListPreference languagePreference) { - sortListPreferenceByValues(languagePreference, 1); - } + sortPreferenceListMenu(Settings.SPOOF_VIDEO_STREAMS_LANGUAGE); + sortPreferenceListMenu(BaseSettings.REVANCED_LANGUAGE); } catch (Exception ex) { Logger.printException(() -> "initialize failure", ex); } } + private void sortPreferenceListMenu(EnumSetting setting) { + Preference preference = findPreference(setting.key); + if (preference instanceof ListPreference languagePreference) { + sortListPreferenceByValues(languagePreference, 1); + } + } + private void setPreferenceScreenToolbar(PreferenceScreen parentScreen) { for (int i = 0, preferenceCount = parentScreen.getPreferenceCount(); i < preferenceCount; i++) { Preference childPreference = parentScreen.getPreference(i); diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt index 5eb29385e3..68c058b7b4 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt @@ -6,6 +6,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.resourcePatch +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patches.all.misc.packagename.setOrGetFallbackPackageName import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch @@ -20,8 +21,12 @@ import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.fix.cairo.disableCairoSettingsPatch import app.revanced.patches.youtube.misc.fix.playbackspeed.fixPlaybackSpeedWhilePlayingPatch import app.revanced.util.* +import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.immutable.ImmutableMethod +import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter import com.android.tools.smali.dexlib2.util.MethodUtil // Used by a fingerprint() from SettingsPatch. @@ -150,6 +155,10 @@ val settingsPatch = bytecodePatch( inputType = InputType.TEXT_MULTI_LINE, tag = "app.revanced.extension.shared.settings.preference.ImportExportPreference", ), + ListPreference( + key = "revanced_language", + summaryKey = null + ) ) setThemeFingerprint.method.let { setThemeMethod -> @@ -189,6 +198,32 @@ val settingsPatch = bytecodePatch( licenseActivityOnCreateFingerprint.classDef.apply { methods.removeIf { it.name != "onCreate" && !MethodUtil.isConstructor(it) } } + + // Add context override to force a specific settings language. + licenseActivityOnCreateFingerprint.classDef.apply { + val attachBaseContext = ImmutableMethod( + type, + "attachBaseContext", + listOf(ImmutableMethodParameter("Landroid/content/Context;", null, null)), + "V", + AccessFlags.PROTECTED.value, + null, + null, + MutableMethodImplementation(3), + ).toMutable().apply { + addInstructions( + """ + invoke-static { p1 }, $activityHookClassDescriptor->getAttachBaseContext(Landroid/content/Context;)Landroid/content/Context; + move-result-object p1 + invoke-super { p0, p1 }, $superclass->attachBaseContext(Landroid/content/Context;)V + return-void + """ + ) + } + + methods.add(attachBaseContext) + } + } finalize { diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt index a408815ca5..fec8800cfb 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/spoof/SpoofVideoStreamsPatch.kt @@ -47,8 +47,11 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch({ tag = "app.revanced.extension.youtube.settings.preference.SpoofStreamingDataSideEffectsPreference" ), ListPreference( - "revanced_spoof_video_streams_language", - summaryKey = null + key = "revanced_spoof_video_streams_language", + summaryKey = null, + // Language strings are declared in Setting patch. + entriesKey = "revanced_language_entries", + entryValuesKey = "revanced_language_entry_values" ), SwitchPreference("revanced_spoof_video_streams_ios_force_avc"), SwitchPreference("revanced_spoof_streaming_data_stats_for_nerds"), diff --git a/patches/src/main/resources/addresources/values/arrays.xml b/patches/src/main/resources/addresources/values/arrays.xml index 670b043b40..a8bf565d9f 100644 --- a/patches/src/main/resources/addresources/values/arrays.xml +++ b/patches/src/main/resources/addresources/values/arrays.xml @@ -1,74 +1,61 @@ - - - - Android VR - @string/revanced_spoof_video_streams_client_type_android_vr_no_auth - Android TV - iOS TV - - - - ANDROID_VR - ANDROID_VR_NO_AUTH - ANDROID_UNPLUGGED - IOS_UNPLUGGED - - - @string/revanced_spoof_video_streams_language_DEFAULT - @string/revanced_spoof_video_streams_language_AR - @string/revanced_spoof_video_streams_language_AZ - @string/revanced_spoof_video_streams_language_BG - @string/revanced_spoof_video_streams_language_BN - @string/revanced_spoof_video_streams_language_CA - @string/revanced_spoof_video_streams_language_CS - @string/revanced_spoof_video_streams_language_DA - @string/revanced_spoof_video_streams_language_DE - @string/revanced_spoof_video_streams_language_EL - @string/revanced_spoof_video_streams_language_EN - @string/revanced_spoof_video_streams_language_ES - @string/revanced_spoof_video_streams_language_ET - @string/revanced_spoof_video_streams_language_FA - @string/revanced_spoof_video_streams_language_FI - @string/revanced_spoof_video_streams_language_FR - @string/revanced_spoof_video_streams_language_GU - @string/revanced_spoof_video_streams_language_HI - @string/revanced_spoof_video_streams_language_HR - @string/revanced_spoof_video_streams_language_HU - @string/revanced_spoof_video_streams_language_ID - @string/revanced_spoof_video_streams_language_IT - @string/revanced_spoof_video_streams_language_JA - @string/revanced_spoof_video_streams_language_KK - @string/revanced_spoof_video_streams_language_KO - @string/revanced_spoof_video_streams_language_LT - @string/revanced_spoof_video_streams_language_LV - @string/revanced_spoof_video_streams_language_MK - @string/revanced_spoof_video_streams_language_MN - @string/revanced_spoof_video_streams_language_MR - @string/revanced_spoof_video_streams_language_MS - @string/revanced_spoof_video_streams_language_MY - @string/revanced_spoof_video_streams_language_NL - @string/revanced_spoof_video_streams_language_OR - @string/revanced_spoof_video_streams_language_PA - @string/revanced_spoof_video_streams_language_PL - @string/revanced_spoof_video_streams_language_PT - @string/revanced_spoof_video_streams_language_RO - @string/revanced_spoof_video_streams_language_RU - @string/revanced_spoof_video_streams_language_SK - @string/revanced_spoof_video_streams_language_SL - @string/revanced_spoof_video_streams_language_SR - @string/revanced_spoof_video_streams_language_SV - @string/revanced_spoof_video_streams_language_SW - @string/revanced_spoof_video_streams_language_TA - @string/revanced_spoof_video_streams_language_TE - @string/revanced_spoof_video_streams_language_TH - @string/revanced_spoof_video_streams_language_TR - @string/revanced_spoof_video_streams_language_UK - @string/revanced_spoof_video_streams_language_UR - @string/revanced_spoof_video_streams_language_VI - @string/revanced_spoof_video_streams_language_ZH - - + + + + @string/revanced_language_DEFAULT + @string/revanced_language_AR + @string/revanced_language_AZ + @string/revanced_language_BG + @string/revanced_language_BN + @string/revanced_language_CA + @string/revanced_language_CS + @string/revanced_language_DA + @string/revanced_language_DE + @string/revanced_language_EL + @string/revanced_language_EN + @string/revanced_language_ES + @string/revanced_language_ET + @string/revanced_language_FA + @string/revanced_language_FI + @string/revanced_language_FR + @string/revanced_language_GU + @string/revanced_language_HI + @string/revanced_language_HR + @string/revanced_language_HU + @string/revanced_language_ID + @string/revanced_language_IT + @string/revanced_language_JA + @string/revanced_language_KK + @string/revanced_language_KO + @string/revanced_language_LT + @string/revanced_language_LV + @string/revanced_language_MK + @string/revanced_language_MN + @string/revanced_language_MR + @string/revanced_language_MS + @string/revanced_language_MY + @string/revanced_language_NL + @string/revanced_language_OR + @string/revanced_language_PA + @string/revanced_language_PL + @string/revanced_language_PT + @string/revanced_language_RO + @string/revanced_language_RU + @string/revanced_language_SK + @string/revanced_language_SL + @string/revanced_language_SR + @string/revanced_language_SV + @string/revanced_language_SW + @string/revanced_language_TA + @string/revanced_language_TE + @string/revanced_language_TH + @string/revanced_language_TR + @string/revanced_language_UK + @string/revanced_language_UR + @string/revanced_language_VI + @string/revanced_language_ZH + + DEFAULT AR AZ @@ -123,6 +110,23 @@ ZH + + + + + Android VR + @string/revanced_spoof_video_streams_client_type_android_vr_no_auth + Android TV + iOS TV + + + + ANDROID_VR + ANDROID_VR_NO_AUTH + ANDROID_UNPLUGGED + IOS_UNPLUGGED + + @string/revanced_spoof_app_version_target_entry_1 diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 6e4bceb3b9..9ffd95837f 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -42,6 +42,62 @@ Second \"item\" text" ReVanced settings reset to default Imported %d settings Import failed: %s + ReVanced language + "Translations for some languages may be missing or incomplete. + +To translate new languages visit translate.revanced.app" + App language + Arabic + Azerbaijani + Bulgarian + Bengali + Catalan + Czech + Danish + German + Greek + English + Spanish + Estonian + Persian + Finnish + French + Gujarati + Hindi + Croatian + Hungarian + Indonesian + Italian + Japanese + Kazakh + Korean + Lithuanian + Latvian + Macedonian + Mongolian + Marathi + Malay + Burmese + Dutch + Odia + Punjabi + Polish + Portuguese + Romanian + Russian + Slovak + Slovene + Serbian + Swedish + Swahili + Tamil + Telugu + Thai + Turkish + Ukrainian + Urdu + Vietnamese + Chinese Import / Export Import / Export ReVanced settings @@ -1343,58 +1399,6 @@ AVC has a maximum resolution of 1080p, Opus audio codec is not available, and vi Client type is shown in Stats for nerds Client is hidden in Stats for nerds VR default audio stream language - App language - Arabic - Azerbaijani - Bulgarian - Bengali - Catalan - Czech - Danish - German - Greek - English - Spanish - Estonian - Persian - Finnish - French - Gujarati - Hindi - Croatian - Hungarian - Indonesian - Italian - Japanese - Kazakh - Korean - Lithuanian - Latvian - Macedonian - Mongolian - Marathi - Malay - Burmese - Dutch - Odia - Punjabi - Polish - Portuguese - Romanian - Russian - Slovak - Slovene - Serbian - Swedish - Swahili - Tamil - Telugu - Thai - Turkish - Ukrainian - Urdu - Vietnamese - Chinese