Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions V2rayNG/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,21 @@ android {
compose = true
}

androidResources {
generateLocaleConfig = true
localeFilters += listOf(
"en",
"zh-rCN",
"zh-rTW",
"vi",
"ru",
"fa",
"ar",
"bn",
"bqi-rIR"
)
}

packaging {
jniLibs {
useLegacyPackaging = true
Expand All @@ -142,6 +157,7 @@ dependencies {

// AndroidX Core Libraries
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)

// Compose Libraries
implementation(platform(libs.androidx.compose.bom))
Expand Down
9 changes: 9 additions & 0 deletions V2rayNG/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@
android:theme="@style/AppThemeDayNight.NoActionBar"
android:usesCleartextTraffic="true">

<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>

<activity
android:name=".ui.main.MainActivity"
android:exported="true"
Expand Down
6 changes: 5 additions & 1 deletion V2rayNG/app/src/main/java/com/v2ray/ang/AngApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package com.v2ray.ang

import android.app.Application
import android.content.Context
import androidx.core.content.ContextCompat
import androidx.work.Configuration
import androidx.work.WorkManager
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.handler.AppLocaleManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.ui.compose.ThemeManager

Expand All @@ -19,7 +21,7 @@ class AngApplication : Application() {
* @param base The base context.
*/
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
super.attachBaseContext(base?.let(ContextCompat::getContextForLanguage))
application = this
}

Expand All @@ -35,6 +37,8 @@ class AngApplication : Application() {

MMKV.initialize(this)

AppLocaleManager.initialize(this)

// Initialize WorkManager with the custom configuration
WorkManager.initialize(this, workManagerConfiguration)

Expand Down
3 changes: 2 additions & 1 deletion V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ object AppConfig {
const val PREF_DOUBLE_COLUMN_DISPLAY = "pref_double_column_display"
const val PREF_GROUP_ALL_DISPLAY = "pref_group_all_display"
const val PREF_LANGUAGE = "pref_language"
const val PREF_APP_LOCALE_MIGRATED = "pref_app_locale_migrated"
const val PREF_UI_MODE_NIGHT = "pref_ui_mode_night"
const val PREF_IPV6_ENABLED = "pref_ipv6_enabled"
const val PREF_PREFER_IPV6 = "pref_prefer_ipv6"
Expand Down Expand Up @@ -178,7 +179,7 @@ object AppConfig {
const val MSG_STATE_STOP_SUCCESS = 41
const val MSG_STATE_RESTART = 5
const val MSG_MEASURE_DELAY = 6
const val MSG_MEASURE_DELAY_SUCCESS = 61
const val MSG_MEASURE_DELAY_RESULT = 61
const val MSG_MEASURE_CONFIG_START = 7
const val MSG_MEASURE_CONFIG_CANCEL = 71
const val MSG_MEASURE_CONFIG_SUCCESS = 72
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ object CoreNativeManager {
/**
* Get V2Ray core version.
*
* @return Version string of the V2Ray core
* @return Version string of the V2Ray core, or null if it cannot be read.
*/
fun getLibVersion(): String {
fun getLibVersion(): String? {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里不要反复的改了,上次已经回滚过一次

@eliotcougar eliotcougar Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Version string rendering moved from core to UI (About and Update screens)

return try {
Libv2ray.checkVersionX()
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "Failed to check V2Ray version", e)
"Unknown"
null
}
}

Expand Down Expand Up @@ -97,4 +97,4 @@ object CoreNativeManager {
throw e
}
}
}
}
28 changes: 17 additions & 11 deletions V2rayNG/app/src/main/java/com/v2ray/ang/core/CoreServiceManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import android.os.ParcelFileDescriptor
import android.system.OsConstants
import androidx.core.content.ContextCompat
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.contracts.IDialerService
import com.v2ray.ang.contracts.ServiceControl
import com.v2ray.ang.dto.ConnectionTestResult
import com.v2ray.ang.dto.OutboundTrafficStat
import com.v2ray.ang.dto.entities.ProfileItem
import com.v2ray.ang.enums.BrowserDialerMode
Expand Down Expand Up @@ -321,28 +321,34 @@ object CoreServiceManager {
time = coreController.measureDelay(SettingsManager.getDelayTestUrl())
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: Failed to measure delay", e)
errorStr = e.message?.substringAfter("\":") ?: "empty message"
errorStr = e.message?.substringAfter("\":").orEmpty()
}
if (time == -1L) {
try {
time = coreController.measureDelay(SettingsManager.getDelayTestUrl(true))
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: Failed to measure delay", e)
errorStr = e.message?.substringAfter("\":") ?: "empty message"
errorStr = e.message?.substringAfter("\":").orEmpty()
}
}

val result = if (time >= 0) {
service.getString(R.string.connection_test_available, time)
} else {
service.getString(R.string.connection_test_error, errorStr)
}
MessageHelper.sendMsg2UI(service, AppConfig.MSG_MEASURE_DELAY_SUCCESS, result)
val result = ConnectionTestResult(
delayMillis = time,
errorMessage = errorStr,
)
MessageHelper.sendMsg2UI(service, AppConfig.MSG_MEASURE_DELAY_RESULT, result)

// Only fetch IP info if the delay test was successful
if (time >= 0) {
SpeedtestManager.getRemoteIPInfo()?.let { ip ->
MessageHelper.sendMsg2UI(service, AppConfig.MSG_MEASURE_DELAY_SUCCESS, "$result\n$ip")
MessageHelper.sendMsg2UI(
service,
AppConfig.MSG_MEASURE_DELAY_RESULT,
result.copy(
country = ip.country,
ipAddress = ip.ipAddress,
),
)
}
}
}
Expand Down Expand Up @@ -492,4 +498,4 @@ object CoreServiceManager {
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.v2ray.ang.dto

import java.io.Serializable

/** Locale-neutral result sent from the daemon for presentation by the UI process. */
data class ConnectionTestResult(
val delayMillis: Long,
val errorMessage: String = "",
val country: String? = null,
val ipAddress: String? = null,
) : Serializable
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
package com.v2ray.ang.dto.entities

data class ServerAffiliationInfo(var testDelayMillis: Long = 0L) {
fun getTestDelayString(): String {
if (testDelayMillis == 0L) {
return ""
}
return testDelayMillis.toString() + "ms"
}
}
data class ServerAffiliationInfo(var testDelayMillis: Long = 0L)
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@ data class ServersCache(
val guid: String,
val profile: ProfileItem,
val testDelayMillis: Long = 0L,
val testDelayString: String = "",
)
58 changes: 47 additions & 11 deletions V2rayNG/app/src/main/java/com/v2ray/ang/enums/Language.kt
Original file line number Diff line number Diff line change
@@ -1,20 +1,56 @@
package com.v2ray.ang.enums

enum class Language(val code: String) {
AUTO("auto"),
ENGLISH("en"),
CHINA("zh-rCN"),
TRADITIONAL_CHINESE("zh-rTW"),
VIETNAMESE("vi"),
RUSSIAN("ru"),
PERSIAN("fa"),
ARABIC("ar"),
BANGLA("bn"),
BAKHTIARI("bqi-rIR");
import androidx.core.os.LocaleListCompat
import java.util.Locale

enum class Language(val code: String, val languageTag: String) {
AUTO("auto", ""),
ENGLISH("en", "en"),
CHINA("zh-rCN", "zh-CN"),
TRADITIONAL_CHINESE("zh-rTW", "zh-TW"),
VIETNAMESE("vi", "vi"),
RUSSIAN("ru", "ru"),
PERSIAN("fa", "fa"),
ARABIC("ar", "ar"),
BANGLA("bn", "bn"),
BAKHTIARI("bqi-rIR", "bqi-IR");

val locale: Locale?
get() = languageTag.takeIf { it.isNotEmpty() }?.let(Locale::forLanguageTag)

fun toLocaleList(): LocaleListCompat =
if (languageTag.isEmpty()) {
LocaleListCompat.getEmptyLocaleList()
} else {
LocaleListCompat.forLanguageTags(languageTag)
}

companion object {
fun fromCode(code: String): Language {
return entries.find { it.code == code } ?: AUTO
}

fun fromLanguageTag(languageTag: String?): Language {
if (languageTag.isNullOrBlank()) return AUTO

val locale = Locale.forLanguageTag(languageTag)
return when (locale.language) {
"en" -> ENGLISH
"zh" -> if (
locale.script == "Hant" || locale.country in setOf("TW", "HK", "MO")
) {
TRADITIONAL_CHINESE
} else {
CHINA
}
"vi" -> VIETNAMESE
"ru" -> RUSSIAN
"fa" -> PERSIAN
"ar" -> ARABIC
"bn" -> BANGLA
"bqi" -> BAKHTIARI
else -> AUTO
}
}
}
}
Loading