From 3932af397ae89a0b30191cd870bd6cddb7a078db Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 28 Dec 2024 12:30:57 +0400 Subject: [PATCH] feat(YouTube): Add in app option to select a preferred language for ReVanced specific text (#4231) --- .../app/revanced/extension/shared/Utils.java | 20 ++- .../AppLanguage.java} | 16 +- .../shared/settings/BaseSettings.java | 5 +- .../shared/spoof/requests/PlayerRoutes.java | 6 +- .../youtube/patches/ExitFullscreenPatch.java | 3 + .../youtube/settings/LicenseActivityHook.java | 17 +++ .../ReVancedPreferenceFragment.java | 15 +- .../youtube/misc/settings/SettingsPatch.kt | 35 +++++ .../misc/spoof/SpoofVideoStreamsPatch.kt | 7 +- .../resources/addresources/values/arrays.xml | 142 +++++++++--------- .../resources/addresources/values/strings.xml | 108 ++++++------- 11 files changed, 234 insertions(+), 140 deletions(-) rename extensions/shared/library/src/main/java/app/revanced/extension/shared/{spoof/AudioStreamLanguage.java => settings/AppLanguage.java} (79%) 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 c7eb05699..037744c7e 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.SynchronousQueue; 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 class Utils { } 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 class Utils { 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 class Utils { 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 d94b0069f..e6d029a2e 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 enum AudioStreamLanguage { 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 136d1d468..8eeedc9a5 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.settings.Setting.parent; 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 9974064e9..0f51009d8 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.Logger; 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 @@ final class PlayerRoutes { // 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 02d53fa69..972c2cd92 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 class ExitFullscreenPatch { 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 1ca0f5c19..1812ca5c5 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 @@ package app.revanced.extension.youtube.settings; 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 @@ import static app.revanced.extension.shared.Utils.getResourceIdentifier; @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 6511fc00c..02804c8c9 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 java.util.List; 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 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment { 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 5eb29385e..68c058b7b 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 a408815ca..fec8800cf 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 670b043b4..a8bf565d9 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 + + + + @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 - - - 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 - - + 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 6e4bceb3b..9ffd95837 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