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 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.
*/

View File

@ -43,6 +43,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 kotlin.text.Regex;
@ -280,14 +282,13 @@ public class Utils {
}
public static Resources getResources() {
if (context != null) {
return context.getResources();
}
Activity mActivity = activityRef.get();
if (mActivity != null) {
return mActivity.getResources();
}
Context mContext = getContext();
if (mContext != null) {
return mContext.getResources();
}
throw new IllegalStateException("Get resources failed");
}
@ -301,7 +302,7 @@ public class Utils {
* @param mContext Context to check locale.
* @return Context with locale applied.
*/
public static Context getLocalizedContextAndSetResources(Context mContext) {
public static Context getLocalizedContext(Context mContext) {
Activity mActivity = activityRef.get();
if (mActivity == null) {
return mContext;
@ -310,19 +311,15 @@ public class Utils {
return null;
}
// Locale of MainActivity.
Locale applicationLocale;
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
// Locale of Application.
Locale applicationLocale = language == AppLanguage.DEFAULT
? mActivity.getResources().getConfiguration().locale
: language.getLocale();
// Locale of Context.
Locale contextLocale;
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;
}
Locale contextLocale = mContext.getResources().getConfiguration().locale;
// If they are identical, no need to override them.
if (applicationLocale == contextLocale) {
@ -350,6 +347,14 @@ public class Utils {
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.

View File

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

View File

@ -49,6 +49,7 @@ fun ResourcePatchContext.baseTranslationsPatch(
sourceDirectory: String,
) {
val resourceDirectory = get("res")
val isYouTube = sourceDirectory == "youtube"
// Check if the custom translation path is valid.
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)
// and also handle locales with "b+" prefix
val filteredAppLanguages = selectedStringResourcesArray.flatMap { language ->
setOf(language, language.replace("-r", "-"),
language.replace("b+", "").replace("+", "-"))
}.toTypedArray()
var filteredAppLanguages = (selectedStringResourcesArray + arrayOf("en"))
.map { language ->
language.replace("-r", "-").replace("b+", "").replace("+", "-")
}.toHashSet().toTypedArray()
// Remove unselected app languages from UI
document("res/xml/locales_config.xml").use { document ->
@ -102,7 +103,7 @@ fun ResourcePatchContext.baseTranslationsPatch(
document.doRecursively { node ->
if (node is Element && node.tagName == "locale") {
node.getAttributeNode("android:name")?.let { attribute ->
if (attribute.textContent != "en" && attribute.textContent !in filteredAppLanguages) {
if (attribute.textContent !in filteredAppLanguages) {
nodesToRemove.add(node)
}
}
@ -114,6 +115,45 @@ fun ResourcePatchContext.baseTranslationsPatch(
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_LIVESTREAMS_ONLY</item>
</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">
<item>@string/revanced_miniplayer_type_entry_0</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.
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_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 -->

View File

@ -838,6 +838,8 @@
<intent android:targetPackage="app.revanced.android.gms" android:targetClass="org.microg.gms.ui.SettingsActivity" />
</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
<PreferenceCategory android:title="@string/revanced_preference_category_experimental_flag" android:layout="@layout/revanced_settings_preferences_category"/>PREFERENCE_CATEGORY: MISC_EXPERIMENTAL_FLAGS -->