feat(YouTube - Settings): Add RVX language setting https://github.com/inotia00/ReVanced_Extended/issues/2680

This commit is contained in:
inotia00 2025-01-17 14:43:57 +09:00
parent 244e792e23
commit b211df4288
8 changed files with 346 additions and 22 deletions

View File

@ -0,0 +1,114 @@
package app.revanced.extension.shared.settings;
import java.util.Locale;
public enum AppLanguage {
/**
* The current app language.
*/
DEFAULT,
// Language codes found in locale_config.xml
// All region specific variants have been removed.
AF,
AM,
AR,
AS,
AZ,
BE,
BG,
BN,
BS,
CA,
CS,
DA,
DE,
EL,
EN,
ES,
ET,
EU,
FA,
FI,
FR,
GL,
GU,
HI,
HE, // App uses obsolete 'IW' and not the modern 'HE' ISO code.
HR,
HU,
HY,
ID,
IS,
IT,
JA,
KA,
KK,
KM,
KN,
KO,
KY,
LO,
LT,
LV,
MK,
ML,
MN,
MR,
MS,
MY,
NE,
NL,
NB,
OR,
PA,
PL,
PT,
RO,
RU,
SI,
SK,
SL,
SQ,
SR,
SV,
SW,
TA,
TE,
TH,
TL,
TR,
UK,
UR,
UZ,
VI,
ZH,
ZU;
private final String language;
AppLanguage() {
language = name().toLowerCase(Locale.US);
}
/**
* @return The 2 letter ISO 639_1 language code.
*/
public String getLanguage() {
// Changing the app language does not force the app to completely restart,
// so the default needs to be the current language and not a static field.
if (this == DEFAULT) {
return Locale.getDefault().getLanguage();
}
return language;
}
public Locale getLocale() {
if (this == DEFAULT) {
return Locale.getDefault();
}
return Locale.forLanguageTag(language);
}
}

View File

@ -22,6 +22,8 @@ public class BaseSettings {
public static final BooleanSetting ENABLE_DEBUG_BUFFER_LOGGING = new BooleanSetting("revanced_enable_debug_buffer_logging", FALSE); public static final BooleanSetting ENABLE_DEBUG_BUFFER_LOGGING = new BooleanSetting("revanced_enable_debug_buffer_logging", FALSE);
public static final BooleanSetting SETTINGS_INITIALIZED = new BooleanSetting("revanced_settings_initialized", FALSE, false, false); public static final BooleanSetting SETTINGS_INITIALIZED = new BooleanSetting("revanced_settings_initialized", FALSE, false, false);
public static final EnumSetting<AppLanguage> REVANCED_LANGUAGE = new EnumSetting<>("revanced_language", AppLanguage.DEFAULT, true);
/** /**
* These settings are used by YouTube and YouTube Music. * These settings are used by YouTube and YouTube Music.
*/ */

View File

@ -43,6 +43,8 @@ import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; 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.BooleanSetting;
import kotlin.text.Regex; import kotlin.text.Regex;
@ -280,14 +282,13 @@ public class Utils {
} }
public static Resources getResources() { public static Resources getResources() {
if (context != null) {
return context.getResources();
}
Activity mActivity = activityRef.get(); Activity mActivity = activityRef.get();
if (mActivity != null) { if (mActivity != null) {
return mActivity.getResources(); return mActivity.getResources();
} }
Context mContext = getContext();
if (mContext != null) {
return mContext.getResources();
}
throw new IllegalStateException("Get resources failed"); throw new IllegalStateException("Get resources failed");
} }
@ -301,7 +302,7 @@ public class Utils {
* @param mContext Context to check locale. * @param mContext Context to check locale.
* @return Context with locale applied. * @return Context with locale applied.
*/ */
public static Context getLocalizedContextAndSetResources(Context mContext) { public static Context getLocalizedContext(Context mContext) {
Activity mActivity = activityRef.get(); Activity mActivity = activityRef.get();
if (mActivity == null) { if (mActivity == null) {
return mContext; return mContext;
@ -310,19 +311,15 @@ public class Utils {
return null; return null;
} }
// Locale of MainActivity. AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
Locale applicationLocale;
// Locale of Application.
Locale applicationLocale = language == AppLanguage.DEFAULT
? mActivity.getResources().getConfiguration().locale
: language.getLocale();
// Locale of Context. // Locale of Context.
Locale contextLocale; Locale contextLocale = mContext.getResources().getConfiguration().locale;
if (isSDKAbove(24)) {
applicationLocale = mActivity.getResources().getConfiguration().getLocales().get(0);
contextLocale = mContext.getResources().getConfiguration().getLocales().get(0);
} else {
applicationLocale = mActivity.getResources().getConfiguration().locale;
contextLocale = mContext.getResources().getConfiguration().locale;
}
// If they are identical, no need to override them. // If they are identical, no need to override them.
if (applicationLocale == contextLocale) { if (applicationLocale == contextLocale) {
@ -350,6 +347,14 @@ public class Utils {
context = appContext; 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. // 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, // Calling the regular printDebug method here can cause a Settings context null pointer exception,
// even though the context is already set before the call. // even though the context is already set before the call.

View File

@ -44,7 +44,7 @@ public class VideoQualitySettingsActivity extends Activity {
@Override @Override
protected void attachBaseContext(Context base) { protected void attachBaseContext(Context base) {
super.attachBaseContext(Utils.getLocalizedContextAndSetResources(base)); super.attachBaseContext(Utils.getLocalizedContext(base));
} }
@Override @Override

View File

@ -49,6 +49,7 @@ fun ResourcePatchContext.baseTranslationsPatch(
sourceDirectory: String, sourceDirectory: String,
) { ) {
val resourceDirectory = get("res") val resourceDirectory = get("res")
val isYouTube = sourceDirectory == "youtube"
// Check if the custom translation path is valid. // Check if the custom translation path is valid.
customTranslations?.takeIf { it.isNotEmpty() }?.let { customLang -> customTranslations?.takeIf { it.isNotEmpty() }?.let { customLang ->
@ -90,10 +91,10 @@ fun ResourcePatchContext.baseTranslationsPatch(
// Filter the app languages to include both versions of locales (with and without 'r', en-rGB and en-GB) // Filter the app languages to include both versions of locales (with and without 'r', en-rGB and en-GB)
// and also handle locales with "b+" prefix // and also handle locales with "b+" prefix
val filteredAppLanguages = selectedStringResourcesArray.flatMap { language -> var filteredAppLanguages = (selectedStringResourcesArray + arrayOf("en"))
setOf(language, language.replace("-r", "-"), .map { language ->
language.replace("b+", "").replace("+", "-")) language.replace("-r", "-").replace("b+", "").replace("+", "-")
}.toTypedArray() }.toHashSet().toTypedArray()
// Remove unselected app languages from UI // Remove unselected app languages from UI
document("res/xml/locales_config.xml").use { document -> document("res/xml/locales_config.xml").use { document ->
@ -102,7 +103,7 @@ fun ResourcePatchContext.baseTranslationsPatch(
document.doRecursively { node -> document.doRecursively { node ->
if (node is Element && node.tagName == "locale") { if (node is Element && node.tagName == "locale") {
node.getAttributeNode("android:name")?.let { attribute -> node.getAttributeNode("android:name")?.let { attribute ->
if (attribute.textContent != "en" && attribute.textContent !in filteredAppLanguages) { if (attribute.textContent !in filteredAppLanguages) {
nodesToRemove.add(node) nodesToRemove.add(node)
} }
} }
@ -114,6 +115,45 @@ fun ResourcePatchContext.baseTranslationsPatch(
node.parentNode?.removeChild(node) node.parentNode?.removeChild(node)
} }
} }
if (!isYouTube) return
filteredAppLanguages = filteredAppLanguages.map { language ->
language.subSequence(0,2).toString().uppercase()
}.toHashSet().toTypedArray()
// Remove unselected app languages from RVX Settings
setOf(
"revanced_language_entries",
"revanced_language_entry_values",
).forEach { attributeName ->
document("res/values/arrays.xml").use { document ->
with(document) {
val nodesToRemove = mutableListOf<Node>()
val resourcesNode = getElementsByTagName("resources").item(0) as Element
for (i in 0 until resourcesNode.childNodes.length) {
val node = resourcesNode.childNodes.item(i) as? Element ?: continue
if (node.getAttribute("name") == attributeName) {
for (j in 0 until node.childNodes.length) {
val item = node.childNodes.item(j) as? Element ?: continue
val text = item.textContent
val length = text.length
if (!text.endsWith("DEFAULT") && text.subSequence(length - 2, length) !in filteredAppLanguages) {
nodesToRemove.add(item)
}
}
}
}
// Remove the collected nodes (avoids NullPointerException)
for (n in nodesToRemove) {
n.parentNode?.removeChild(n)
}
}
}
}
} }
/** /**

View File

@ -183,6 +183,114 @@
<item>MEMBERSHIPS_SHORTS_ONLY</item> <item>MEMBERSHIPS_SHORTS_ONLY</item>
<item>MEMBERSHIPS_LIVESTREAMS_ONLY</item> <item>MEMBERSHIPS_LIVESTREAMS_ONLY</item>
</string-array> </string-array>
<string-array name="revanced_language_entries">
<item>@string/revanced_language_DEFAULT</item>
<item>@string/revanced_language_AR</item>
<item>@string/revanced_language_AZ</item>
<item>@string/revanced_language_BG</item>
<item>@string/revanced_language_BN</item>
<item>@string/revanced_language_CA</item>
<item>@string/revanced_language_CS</item>
<item>@string/revanced_language_DA</item>
<item>@string/revanced_language_DE</item>
<item>@string/revanced_language_EL</item>
<item>@string/revanced_language_EN</item>
<item>@string/revanced_language_ES</item>
<item>@string/revanced_language_ET</item>
<item>@string/revanced_language_FA</item>
<item>@string/revanced_language_FI</item>
<item>@string/revanced_language_FR</item>
<item>@string/revanced_language_GU</item>
<item>@string/revanced_language_HI</item>
<item>@string/revanced_language_HR</item>
<item>@string/revanced_language_HU</item>
<item>@string/revanced_language_ID</item>
<item>@string/revanced_language_IT</item>
<item>@string/revanced_language_JA</item>
<item>@string/revanced_language_KK</item>
<item>@string/revanced_language_KO</item>
<item>@string/revanced_language_LT</item>
<item>@string/revanced_language_LV</item>
<item>@string/revanced_language_MK</item>
<item>@string/revanced_language_MN</item>
<item>@string/revanced_language_MR</item>
<item>@string/revanced_language_MS</item>
<item>@string/revanced_language_MY</item>
<item>@string/revanced_language_NL</item>
<item>@string/revanced_language_OR</item>
<item>@string/revanced_language_PA</item>
<item>@string/revanced_language_PL</item>
<item>@string/revanced_language_PT</item>
<item>@string/revanced_language_RO</item>
<item>@string/revanced_language_RU</item>
<item>@string/revanced_language_SK</item>
<item>@string/revanced_language_SL</item>
<item>@string/revanced_language_SR</item>
<item>@string/revanced_language_SV</item>
<item>@string/revanced_language_SW</item>
<item>@string/revanced_language_TA</item>
<item>@string/revanced_language_TE</item>
<item>@string/revanced_language_TH</item>
<item>@string/revanced_language_TR</item>
<item>@string/revanced_language_UK</item>
<item>@string/revanced_language_UR</item>
<item>@string/revanced_language_VI</item>
<item>@string/revanced_language_ZH</item>
</string-array>
<string-array name="revanced_language_entry_values">
<item>DEFAULT</item>
<item>AR</item>
<item>AZ</item>
<item>BG</item>
<item>BN</item>
<item>CA</item>
<item>CS</item>
<item>DA</item>
<item>DE</item>
<item>EL</item>
<item>EN</item>
<item>ES</item>
<item>ET</item>
<item>FA</item>
<item>FI</item>
<item>FR</item>
<item>GU</item>
<item>HI</item>
<item>HR</item>
<item>HU</item>
<item>ID</item>
<item>IT</item>
<item>JA</item>
<item>KK</item>
<item>KO</item>
<item>LT</item>
<item>LV</item>
<item>MK</item>
<item>MN</item>
<item>MR</item>
<item>MS</item>
<item>MY</item>
<item>NL</item>
<item>OR</item>
<item>PA</item>
<item>PL</item>
<item>PT</item>
<item>RO</item>
<item>RU</item>
<item>SK</item>
<item>SL</item>
<item>SR</item>
<item>SV</item>
<item>SW</item>
<item>TA</item>
<item>TE</item>
<item>TH</item>
<item>TR</item>
<item>UK</item>
<item>UR</item>
<item>VI</item>
<item>ZH</item>
</string-array>
<string-array name="revanced_miniplayer_type_19_43_entries"> <string-array name="revanced_miniplayer_type_19_43_entries">
<item>@string/revanced_miniplayer_type_entry_0</item> <item>@string/revanced_miniplayer_type_entry_0</item>
<item>@string/revanced_miniplayer_type_entry_1</item> <item>@string/revanced_miniplayer_type_entry_1</item>

View File

@ -22,6 +22,59 @@
<string name="revanced_external_downloader_not_installed_dialog_message">"%1$s is not installed. <string name="revanced_external_downloader_not_installed_dialog_message">"%1$s is not installed.
Please download %2$s from the website."</string> Please download %2$s from the website."</string>
<string name="revanced_external_downloader_not_installed_warning">%s is not installed. Please install it.</string> <string name="revanced_external_downloader_not_installed_warning">%s is not installed. Please install it.</string>
<string name="revanced_language_title">RVX language</string>
<string name="revanced_language_DEFAULT">App language</string>
<string name="revanced_language_AR">Arabic</string>
<string name="revanced_language_AZ">Azerbaijani</string>
<string name="revanced_language_BG">Bulgarian</string>
<string name="revanced_language_BN">Bengali</string>
<string name="revanced_language_CA">Catalan</string>
<string name="revanced_language_CS">Czech</string>
<string name="revanced_language_DA">Danish</string>
<string name="revanced_language_DE">German</string>
<string name="revanced_language_EL">Greek</string>
<string name="revanced_language_EN">English</string>
<string name="revanced_language_ES">Spanish</string>
<string name="revanced_language_ET">Estonian</string>
<string name="revanced_language_FA">Persian</string>
<string name="revanced_language_FI">Finnish</string>
<string name="revanced_language_FR">French</string>
<string name="revanced_language_GU">Gujarati</string>
<string name="revanced_language_HI">Hindi</string>
<string name="revanced_language_HR">Croatian</string>
<string name="revanced_language_HU">Hungarian</string>
<string name="revanced_language_ID">Indonesian</string>
<string name="revanced_language_IT">Italian</string>
<string name="revanced_language_JA">Japanese</string>
<string name="revanced_language_KK">Kazakh</string>
<string name="revanced_language_KO">Korean</string>
<string name="revanced_language_LT">Lithuanian</string>
<string name="revanced_language_LV">Latvian</string>
<string name="revanced_language_MK">Macedonian</string>
<string name="revanced_language_MN">Mongolian</string>
<string name="revanced_language_MR">Marathi</string>
<string name="revanced_language_MS">Malay</string>
<string name="revanced_language_MY">Burmese</string>
<string name="revanced_language_NL">Dutch</string>
<string name="revanced_language_OR">Odia</string>
<string name="revanced_language_PA">Punjabi</string>
<string name="revanced_language_PL">Polish</string>
<string name="revanced_language_PT">Portuguese</string>
<string name="revanced_language_RO">Romanian</string>
<string name="revanced_language_RU">Russian</string>
<string name="revanced_language_SK">Slovak</string>
<string name="revanced_language_SL">Slovene</string>
<string name="revanced_language_SR">Serbian</string>
<string name="revanced_language_SV">Swedish</string>
<string name="revanced_language_SW">Swahili</string>
<string name="revanced_language_TA">Tamil</string>
<string name="revanced_language_TE">Telugu</string>
<string name="revanced_language_TH">Thai</string>
<string name="revanced_language_TR">Turkish</string>
<string name="revanced_language_UK">Ukrainian</string>
<string name="revanced_language_UR">Urdu</string>
<string name="revanced_language_VI">Vietnamese</string>
<string name="revanced_language_ZH">Chinese</string>
<!-- PreferenceScreen: Ads --> <!-- PreferenceScreen: Ads -->

View File

@ -838,6 +838,8 @@
<intent android:targetPackage="app.revanced.android.gms" android:targetClass="org.microg.gms.ui.SettingsActivity" /> <intent android:targetPackage="app.revanced.android.gms" android:targetClass="org.microg.gms.ui.SettingsActivity" />
</Preference>PREFERENCE: GMS_CORE_SETTINGS --> </Preference>PREFERENCE: GMS_CORE_SETTINGS -->
<ListPreference android:entries="@array/revanced_language_entries" android:title="@string/revanced_language_title" android:key="revanced_language" android:entryValues="@array/revanced_language_entry_values" />
<!-- PREFERENCE_CATEGORY: MISC_EXPERIMENTAL_FLAGS <!-- PREFERENCE_CATEGORY: MISC_EXPERIMENTAL_FLAGS
<PreferenceCategory android:title="@string/revanced_preference_category_experimental_flag" android:layout="@layout/revanced_settings_preferences_category"/>PREFERENCE_CATEGORY: MISC_EXPERIMENTAL_FLAGS --> <PreferenceCategory android:title="@string/revanced_preference_category_experimental_flag" android:layout="@layout/revanced_settings_preferences_category"/>PREFERENCE_CATEGORY: MISC_EXPERIMENTAL_FLAGS -->