diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt index 8b8b0fedaa..6ee26f6285 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt @@ -179,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 diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/core/CoreServiceManager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/core/CoreServiceManager.kt index 21319210a3..77e462b2db 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/core/CoreServiceManager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/core/CoreServiceManager.kt @@ -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 @@ -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, + ), + ) } } } @@ -492,4 +498,4 @@ object CoreServiceManager { } } } -} \ No newline at end of file +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/ConnectionTestResult.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/ConnectionTestResult.kt new file mode 100644 index 0000000000..a30ad156f1 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/ConnectionTestResult.kt @@ -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 diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ServerAffiliationInfo.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ServerAffiliationInfo.kt index ce21a3d288..2f78334458 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ServerAffiliationInfo.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ServerAffiliationInfo.kt @@ -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" - } -} \ No newline at end of file +data class ServerAffiliationInfo(var testDelayMillis: Long = 0L) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ServersCache.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ServersCache.kt index 05df615189..d25e087da4 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ServersCache.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ServersCache.kt @@ -4,5 +4,4 @@ data class ServersCache( val guid: String, val profile: ProfileItem, val testDelayMillis: Long = 0L, - val testDelayString: String = "", ) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/SpeedtestManager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/SpeedtestManager.kt index b1e6dda743..524ee97df6 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/SpeedtestManager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/SpeedtestManager.kt @@ -13,6 +13,11 @@ import java.net.UnknownHostException object SpeedtestManager { + data class RemoteEndpointInfo( + val country: String?, + val ipAddress: String?, + ) + /** * Measures the time taken to establish a TCP connection to a given URL and port. * @@ -48,7 +53,7 @@ object SpeedtestManager { return -1 } - fun getRemoteIPInfo(): String? { + fun getRemoteIPInfo(): RemoteEndpointInfo? { val url = MmkvManager.decodeSettingsString(AppConfig.PREF_IP_API_URL) .takeIf { !it.isNullOrBlank() } ?: AppConfig.IP_API_URL @@ -81,6 +86,9 @@ object SpeedtestManager { ipInfo.location?.country_code ).firstOrNull { !it.isNullOrBlank() } - return "(${country ?: "unknown"}) ${ip ?: "unknown"}" + return RemoteEndpointInfo( + country = country, + ipAddress = ip, + ) } } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/checkupdate/CheckUpdateViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/checkupdate/CheckUpdateViewModel.kt index 8d598ed449..524993be01 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/checkupdate/CheckUpdateViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/checkupdate/CheckUpdateViewModel.kt @@ -42,12 +42,8 @@ class CheckUpdateViewModel(application: Application) : BaseViewModel(application toastSuccess(R.string.update_already_latest_version) } } catch (e: Exception) { - LogUtil.e(AppConfig.TAG, "Failed to check for updates: ${e.message}") - if (e.message == null) { - toastError(R.string.toast_failure) - } else { - toastError(e.message.orEmpty()) - } + LogUtil.e(AppConfig.TAG, "Failed to check for updates", e) + toastError(R.string.toast_failure) } } } @@ -55,4 +51,4 @@ class CheckUpdateViewModel(application: Application) : BaseViewModel(application fun dismissUpdateDialog() { _showUpdateDialog.value = false } -} \ No newline at end of file +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/compose/FormFields.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/compose/FormFields.kt index 7ab3f1e9a0..ea74eeafd5 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/compose/FormFields.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/compose/FormFields.kt @@ -78,6 +78,7 @@ fun FormDropdownField( editable: Boolean = false, enabled: Boolean = true, placeholder: String? = null, + supportingText: String? = null, ) { var expanded by rememberSaveable { mutableStateOf(false) } val menuScrollState = rememberScrollState() @@ -104,6 +105,7 @@ fun FormDropdownField( enabled = enabled, label = { Text(label) }, placeholder = { if (placeholder != null) Text(placeholder) }, + supportingText = supportingText?.let { { Text(it) } }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, colors = OutlinedTextFieldDefaults.colors( focusedContainerColor = Color.Transparent, diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/logcat/LogcatActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/logcat/LogcatActivity.kt index dbf1694821..32e971edcb 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/logcat/LogcatActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/logcat/LogcatActivity.kt @@ -37,12 +37,14 @@ import androidx.compose.ui.unit.dp import androidx.core.content.FileProvider import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope +import com.v2ray.ang.AppConfig import com.v2ray.ang.R -import com.v2ray.ang.extension.toast +import com.v2ray.ang.extension.toastError import com.v2ray.ang.ui.base.BaseComponentActivity import com.v2ray.ang.ui.compose.AppTopBar import com.v2ray.ang.ui.compose.ItemDivider import com.v2ray.ang.ui.compose.verticalScrollbar +import com.v2ray.ang.util.LogUtil import com.v2ray.ang.util.Utils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -91,8 +93,9 @@ class LogcatActivity : BaseComponentActivity() { uri to logFile.name } catch (e: Exception) { + LogUtil.e(AppConfig.TAG, "Failed to share Logcat", e) withContext(Dispatchers.Main) { - toast(e.localizedMessage ?: e.toString()) + toastError(R.string.toast_failure) } return@launch } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainContract.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainContract.kt index 103f9bf261..8df9ece89b 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainContract.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainContract.kt @@ -1,8 +1,18 @@ package com.v2ray.ang.ui.main +import com.v2ray.ang.dto.ConnectionTestResult import com.v2ray.ang.dto.GroupMapItem import com.v2ray.ang.dto.LocateTarget +/** Locale-neutral state formatted only when it reaches the main UI. */ +sealed interface MainStatus { + data object Disconnected : MainStatus + data object Connected : MainStatus + data object Testing : MainStatus + data class TestProgress(val progress: String) : MainStatus + data class ConnectionTest(val result: ConnectionTestResult) : MainStatus +} + /** * Main UI state */ @@ -12,7 +22,7 @@ data class MainUiState( val selectedGuid: String? = null, val isRunning: Boolean = false, val isTesting: Boolean = false, - val statusText: String = "", + val status: MainStatus = MainStatus.Disconnected, val locateTarget: LocateTarget? = null, val confirmRemove: Boolean = false, val doubleColumnDisplay: Boolean = false, diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainRepository.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainRepository.kt index d1e0e0fb7b..e272f747b8 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainRepository.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainRepository.kt @@ -8,12 +8,14 @@ import androidx.core.content.ContextCompat import com.v2ray.ang.AngApplication import com.v2ray.ang.AppConfig import com.v2ray.ang.R +import com.v2ray.ang.dto.ConnectionTestResult import com.v2ray.ang.dto.SubscriptionUpdateResult import com.v2ray.ang.dto.TestServiceMessage import com.v2ray.ang.dto.entities.ProfileItem import com.v2ray.ang.dto.entities.ServerAffiliationInfo import com.v2ray.ang.dto.entities.SubscriptionCache import com.v2ray.ang.dto.entities.SubscriptionItem +import com.v2ray.ang.extension.serializable import com.v2ray.ang.handler.AngConfigManager import com.v2ray.ang.handler.AppLocaleManager import com.v2ray.ang.handler.MmkvManager @@ -52,14 +54,12 @@ class MainRepository( AppConfig.MSG_STATE_RUNNING -> MainServiceEvent.StateRunning AppConfig.MSG_STATE_NOT_RUNNING -> MainServiceEvent.StateNotRunning AppConfig.MSG_STATE_START_SUCCESS -> MainServiceEvent.StateStartSuccess - AppConfig.MSG_STATE_START_FAILURE -> MainServiceEvent.StateStartFailure( - safeIntent.getStringExtra("content").orEmpty() - ) + AppConfig.MSG_STATE_START_FAILURE -> MainServiceEvent.StateStartFailure AppConfig.MSG_STATE_STOP_SUCCESS -> MainServiceEvent.StateStopSuccess - AppConfig.MSG_MEASURE_DELAY_SUCCESS -> MainServiceEvent.MeasureDelaySuccess( - safeIntent.getStringExtra("content").orEmpty() - ) + AppConfig.MSG_MEASURE_DELAY_RESULT -> safeIntent + .serializable("content") + ?.let { MainServiceEvent.MeasureDelayResult(it) } AppConfig.MSG_MEASURE_CONFIG_SUCCESS -> MainServiceEvent.MeasureConfigSuccess AppConfig.MSG_MEASURE_CONFIG_NOTIFY -> MainServiceEvent.MeasureConfigNotify( diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainScreen.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainScreen.kt index 35b2cbe8bb..ee2b149d50 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainScreen.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainScreen.kt @@ -44,7 +44,7 @@ fun MainScreen( val groups = uiState.groups val isLoading by mainViewModel.isLoading.collectAsStateWithLifecycle() val isRunning = uiState.isRunning - val displayText = uiState.statusText + val displayText = mainViewModel.formatStatus(uiState.status) val selectedGuid = uiState.selectedGuid val doubleColumnDisplay = uiState.doubleColumnDisplay val confirmRemove = uiState.confirmRemove diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainServerPager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainServerPager.kt index dad6ef8364..bf36175418 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainServerPager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainServerPager.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.LineBreak import androidx.compose.ui.text.style.TextOverflow @@ -249,7 +250,6 @@ private fun ServerItemRow( statistics = profile.description.nullIfBlank() ?: AngConfigManager.generateDescription(profile), typeDescription = getProtocolDescription(profile), - testResult = serverCache.testDelayString, testDelayMillis = serverCache.testDelayMillis, isSelected = serverCache.guid == selectedGuid, subscriptionRemarks = subRemarks, @@ -283,7 +283,6 @@ private fun ServerItemColumn( remarks = profile.remarks, statistics = profile.description.nullIfBlank() ?: AngConfigManager.generateDescription(profile), typeDescription = getProtocolDescription(profile), - testResult = serverCache.testDelayString, testDelayMillis = serverCache.testDelayMillis, isSelected = serverCache.guid == selectedGuid, subscriptionRemarks = subRemarks, @@ -303,7 +302,6 @@ fun ServerListItem( remarks: String, statistics: String, typeDescription: String, - testResult: String, testDelayMillis: Long, isSelected: Boolean, subscriptionRemarks: String, @@ -316,6 +314,12 @@ fun ServerListItem( modifier: Modifier = Modifier, dragModifier: Modifier = Modifier ) { + val testResult = if (testDelayMillis == 0L) { + "" + } else { + stringResource(R.string.server_test_delay_value, testDelayMillis) + } + Row( modifier = modifier .fillMaxWidth() diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainServiceEvent.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainServiceEvent.kt index 105092c7ac..9da9eff1a8 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainServiceEvent.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainServiceEvent.kt @@ -1,12 +1,14 @@ package com.v2ray.ang.ui.main +import com.v2ray.ang.dto.ConnectionTestResult + sealed class MainServiceEvent { data object StateRunning : MainServiceEvent() data object StateNotRunning : MainServiceEvent() data object StateStartSuccess : MainServiceEvent() - data class StateStartFailure(val errorMessage: String) : MainServiceEvent() + data object StateStartFailure : MainServiceEvent() data object StateStopSuccess : MainServiceEvent() - data class MeasureDelaySuccess(val content: String) : MainServiceEvent() + data class MeasureDelayResult(val result: ConnectionTestResult) : MainServiceEvent() data object MeasureConfigSuccess : MainServiceEvent() data class MeasureConfigNotify(val progress: String) : MainServiceEvent() data class MeasureConfigFinish(val finishedCount: String?) : MainServiceEvent() diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainViewModel.kt index c1114af800..83f0814d0c 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.v2ray.ang.AppConfig import com.v2ray.ang.R +import com.v2ray.ang.dto.ConnectionTestResult import com.v2ray.ang.dto.GroupMapItem import com.v2ray.ang.dto.LocateTarget import com.v2ray.ang.dto.TestServiceMessage @@ -45,15 +46,11 @@ class MainViewModel( private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default private val preloadDispatcher: CoroutineDispatcher = Dispatchers.IO.limitedParallelism(1) - private val disconnectedText: String = dataSource.getString(R.string.connection_not_connected) - private val connectedText: String = dataSource.getString(R.string.connection_connected) - // ---------- UI state ---------- private val _uiState = MutableStateFlow( MainUiState( selectedGroupId = dataSource.getSelectedSubscriptionId(), selectedGuid = dataSource.getSelectServer(), - statusText = disconnectedText, confirmRemove = dataSource.getConfirmRemove(), doubleColumnDisplay = dataSource.getDoubleColumnDisplay() ) @@ -105,19 +102,14 @@ class MainViewModel( updateRunningState(true) } - is MainServiceEvent.StateStartFailure -> { - val error = event.errorMessage - if (error.isNotBlank()) { - toastError(error) - } else { - toastError(R.string.toast_services_failure) - } + MainServiceEvent.StateStartFailure -> { + toastError(R.string.toast_services_failure) updateRunningState(false) } MainServiceEvent.StateStopSuccess -> updateRunningState(false) - is MainServiceEvent.MeasureDelaySuccess -> { - _uiState.update { it.copy(statusText = event.content) } + is MainServiceEvent.MeasureDelayResult -> { + _uiState.update { it.copy(status = MainStatus.ConnectionTest(event.result)) } } MainServiceEvent.MeasureConfigSuccess -> { @@ -129,14 +121,7 @@ class MainViewModel( } is MainServiceEvent.MeasureConfigNotify -> { - _uiState.update { - it.copy( - statusText = dataSource.getString( - R.string.connection_running_task_left, - event.progress - ) - ) - } + _uiState.update { it.copy(status = MainStatus.TestProgress(event.progress)) } } is MainServiceEvent.MeasureConfigFinish -> { @@ -145,6 +130,37 @@ class MainViewModel( } } + internal fun formatStatus(status: MainStatus): String = when (status) { + MainStatus.Disconnected -> dataSource.getString(R.string.connection_not_connected) + MainStatus.Connected -> dataSource.getString(R.string.connection_connected) + MainStatus.Testing -> dataSource.getString(R.string.connection_test_testing) + is MainStatus.TestProgress -> dataSource.getString( + R.string.connection_running_task_left, + status.progress + ) + + is MainStatus.ConnectionTest -> formatConnectionTestResult(status.result) + } + + private fun formatConnectionTestResult(result: ConnectionTestResult): String { + val status = if (result.delayMillis >= 0) { + val delay = dataSource.getString(R.string.server_test_delay_value, result.delayMillis) + dataSource.getString(R.string.connection_test_available, delay) + } else { + val detail = result.errorMessage.ifBlank { + dataSource.getString(R.string.connection_test_empty_message) + } + dataSource.getString(R.string.connection_test_error, detail) + } + + if (result.delayMillis < 0 || (result.country == null && result.ipAddress == null)) { + return status + } + + val unknown = dataSource.getString(R.string.value_unknown) + return "$status\n(${result.country ?: unknown}) ${result.ipAddress ?: unknown}" + } + // ---------- Public state accessors ---------- fun serversForGroup(groupId: String): StateFlow> = groupPageFlows.computeIfAbsent(groupId) { MutableStateFlow(emptyList()) } @@ -235,8 +251,7 @@ class MainViewModel( ServersCache( guid = guid, profile = profile.copy(), - testDelayMillis = affiliation?.testDelayMillis ?: 0L, - testDelayString = affiliation?.getTestDelayString().orEmpty() + testDelayMillis = affiliation?.testDelayMillis ?: 0L ) } @@ -665,7 +680,7 @@ class MainViewModel( _uiState.update { it.copy( isTesting = false, - statusText = if (it.isRunning) connectedText else disconnectedText + status = if (it.isRunning) MainStatus.Connected else MainStatus.Disconnected ) } } @@ -683,7 +698,7 @@ class MainViewModel( _uiState.update { it.copy( isTesting = true, - statusText = dataSource.getString(R.string.connection_test_testing) + status = MainStatus.Testing ) } viewModelScope.launch(ioDispatcher) { @@ -700,11 +715,7 @@ class MainViewModel( } fun testCurrentServerRealPing() { - _uiState.update { - it.copy( - statusText = dataSource.getString(R.string.connection_test_testing) - ) - } + _uiState.update { it.copy(status = MainStatus.Testing) } dataSource.testCurrentServerRealPing() } @@ -715,7 +726,7 @@ class MainViewModel( _uiState.update { it.copy( isTesting = false, - statusText = if (it.isRunning) connectedText else disconnectedText + status = if (it.isRunning) MainStatus.Connected else MainStatus.Disconnected ) } reloadAllGroups(_uiState.value.groups.map { it.id }) @@ -751,8 +762,8 @@ class MainViewModel( _uiState.update { state -> state.copy( isRunning = running, - statusText = if (!clearTestingText && state.isTesting) state.statusText - else if (running) connectedText else disconnectedText + status = if (!clearTestingText && state.isTesting) state.status + else if (running) MainStatus.Connected else MainStatus.Disconnected ) } } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/routing/RoutingEditActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/routing/RoutingEditActivity.kt index 45d7e2ba30..fc03292ba5 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/routing/RoutingEditActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/routing/RoutingEditActivity.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.ScaffoldDefaults import androidx.compose.material3.Text @@ -118,6 +119,7 @@ fun RoutingEditScreen( onDelete: () -> Unit ) { val context = LocalContext.current + val processSelectTitle = stringResource(R.string.routing_settings_process_select) val scrollState = rememberScrollState() var remarks by rememberSaveable { mutableStateOf(initial?.remarks ?: "") } @@ -220,21 +222,27 @@ fun RoutingEditScreen( checked = locked, onCheckedChange = { locked = it } ) + Text( + text = stringResource(R.string.routing_settings_tips), + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) FormTextField( label = stringResource(R.string.routing_settings_domain), - placeholder = stringResource(R.string.routing_settings_tips), + placeholder = stringResource(R.string.routing_settings_comma_tip), value = domain, onValueChange = { domain = it } ) FormTextField( label = stringResource(R.string.routing_settings_ip), - placeholder = stringResource(R.string.routing_settings_tips), + placeholder = stringResource(R.string.routing_settings_comma_tip), value = ip, onValueChange = { ip = it } ) FormTextField( label = stringResource(R.string.routing_settings_process), - placeholder = stringResource(R.string.routing_settings_tips), + placeholder = stringResource(R.string.routing_settings_comma_tip), value = processText, onValueChange = { processText = it }, enabled = canUseProcess @@ -251,7 +259,7 @@ fun RoutingEditScreen( AppPickerActivity.createIntent( context = context, selectedPackages = current, - title = context.getString(R.string.routing_settings_process_select) + title = processSelectTitle ) ) }, @@ -262,7 +270,7 @@ fun RoutingEditScreen( contentDescription = null ) Spacer(modifier = Modifier.width(8.dp)) - Text(stringResource(R.string.routing_settings_process_select)) + Text(processSelectTitle) } } FormTextField( diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/subscription/SubEditActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/subscription/SubEditActivity.kt index 977b754fea..545fb3c7a4 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/subscription/SubEditActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/subscription/SubEditActivity.kt @@ -227,7 +227,8 @@ fun SubEditScreen( value = prevProfile, options = profileSuggestions, onValueChange = { prevProfile = it }, - editable = true + editable = true, + supportingText = stringResource(R.string.sub_setting_entry_proxy_tip) ) FormDropdownField( label = stringResource(R.string.sub_setting_next_profile), @@ -235,7 +236,8 @@ fun SubEditScreen( value = nextProfile, options = profileSuggestions, onValueChange = { nextProfile = it }, - editable = true + editable = true, + supportingText = stringResource(R.string.sub_setting_exit_proxy_tip) ) } } diff --git a/V2rayNG/app/src/main/res/values-ar/strings.xml b/V2rayNG/app/src/main/res/values-ar/strings.xml index 1c6cc4bae7..82f87f12ae 100644 --- a/V2rayNG/app/src/main/res/values-ar/strings.xml +++ b/V2rayNG/app/src/main/res/values-ar/strings.xml @@ -327,9 +327,11 @@ تفعيل التحديث تفعيل التحديث التلقائي السماح بعنوان HTTP غير آمن - ملاحظات تكوين الوكيل السابق - ملاحظات تكوين الوكيل التالي + وكيل الدخول + وكيل الخروج يجب أن تكون ملاحظات التكوين موجودة وفريدة + يُضاف الوكيل المحدد قبل كل ملف تعريف في هذا الاشتراك كحلقة الدخول في سلسلة الوكلاء لذلك الملف. + يُضاف الوكيل المحدد بعد كل ملف تعريف في هذا الاشتراك كحلقة الخروج في سلسلة الوكلاء لذلك الملف. فاصل التحديث غير صالح. الحد الأدنى هو 15 دقيقة. تحديث الاشتراك اختبار تأخير TCP (TCPing) @@ -355,7 +357,8 @@ استراتيجية النطاق إعدادات التوجيه - افصل بعلامة الفاصلة (,) واختر أحد: domain أو ip أو process + لا تُطبَّق القاعدة إلا إذا تحققت جميع شروط المطابقة المحددة معًا (AND). تُفحص القواعد من الأعلى إلى الأسفل، وتُطبَّق أول قاعدة متطابقة. + افصل بين الإدخالات المتعددة بفواصل (,). حفظ مسح إعدادات قاعدة التوجيه @@ -380,7 +383,8 @@ التحقق من الاتصال يجري الاختبار… جارٍ اختبار %d من التكوينات… - نجاح: استغرق اتصال HTTP %dms + تم الاتصال بنجاح خلال %1$s + %1$d ملي ثانية فشل اختبار الاتصال: %s الإنترنت غير متاح رمز الخطأ: #%d @@ -502,14 +506,6 @@ نسخ السجل مسح السجل إضافة قاعدة توجيه - لم يتم تحديد تكوين - تعذرت قراءة التكوين المحدد - تعذر إعداد التكوين المحدد - تعذر إنشاء تكوين النواة - تعذر إنشاء تكوين النواة: %1$s - تعذر إنشاء تكوين النواة لاختبار السرعة: %1$s - التكوين المخصص فارغ - تعذر تشغيل النواة بدء الخدمة إيقاف الخدمة رجوع diff --git a/V2rayNG/app/src/main/res/values-bn/strings.xml b/V2rayNG/app/src/main/res/values-bn/strings.xml index c1d883afd3..5402b33a42 100644 --- a/V2rayNG/app/src/main/res/values-bn/strings.xml +++ b/V2rayNG/app/src/main/res/values-bn/strings.xml @@ -327,9 +327,11 @@ আপডেট সক্রিয় করুন স্বয়ংক্রিয় আপডেট সক্রিয় করুন অনিরাপদ HTTP ঠিকানা অনুমোদন করুন - আগের প্রক্সি কনফিগারেশনের মন্তব্য - পরবর্তী প্রক্সি কনফিগারেশনের মন্তব্য + প্রবেশ প্রক্সি + প্রস্থান প্রক্সি কনফিগারেশনের মন্তব্যটি বিদ্যমান ও অনন্য হতে হবে + নির্বাচিত প্রক্সিটি এই সাবস্ক্রিপশনের প্রতিটি প্রোফাইলের আগে সেই প্রোফাইলের প্রক্সি চেইনের প্রবেশ লিঙ্ক হিসেবে যোগ করা হবে। + নির্বাচিত প্রক্সিটি এই সাবস্ক্রিপশনের প্রতিটি প্রোফাইলের পরে সেই প্রোফাইলের প্রক্সি চেইনের প্রস্থান লিঙ্ক হিসেবে যোগ করা হবে। আপডেটের বিরতি সঠিক নয়। সর্বনিম্ন ১৫ মিনিট। সাবস্ক্রিপশন আপডেট TCP বিলম্ব পরীক্ষা করুন (TCPing) @@ -355,7 +357,8 @@ ডোমেইন কৌশল রাউটিং সেটিংস - কমা (,) দিয়ে আলাদা করুন। domain, ip বা process-এ একটি নির্বাচন করুন + সব নির্ধারিত মিলের শর্ত একসঙ্গে পূরণ হলেই কেবল নিয়মটি প্রয়োগ হবে (AND)। নিয়মগুলি ওপর থেকে নিচে পরীক্ষা করা হয়; প্রথম মিলে যাওয়া নিয়মটি প্রয়োগ করা হয়। + একাধিক এন্ট্রি কমা (,) দিয়ে আলাদা করুন। সেভ করুন মুছে ফেলুন রাউটিং নিয়মের সেটিংস @@ -380,7 +383,8 @@ সংযোগ পরীক্ষা করুন পরীক্ষা চলছে… %dটি কনফিগারেশন পরীক্ষা করা হচ্ছে… - সফল: HTTP সংযোগ নিয়েছে %dms + সংযোগ সফল হয়েছে; সময় লেগেছে %1$s + %1$d মিলিসেকেন্ড সংযোগ পরীক্ষা ব্যর্থ: %s ইন্টারনেট উপলব্ধ নয় ত্রুটি কোড: #%d @@ -502,14 +506,6 @@ লগ কপি করুন লগ মুছুন রাউটিং নিয়ম যোগ করুন - কোনো কনফিগারেশন নির্বাচন করা হয়নি - নির্বাচিত কনফিগারেশন পড়তে ব্যর্থ - নির্বাচিত কনফিগারেশন প্রস্তুত করতে ব্যর্থ - কোর কনফিগারেশন তৈরি করতে ব্যর্থ - কোর কনফিগারেশন তৈরি করতে ব্যর্থ: %1$s - স্পিড টেস্টের জন্য কোর কনফিগারেশন তৈরি করতে ব্যর্থ: %1$s - কাস্টম কনফিগারেশন খালি - কোর চালু করতে ব্যর্থ সার্ভিস শুরু করুন সার্ভিস বন্ধ করুন ফিরে যান diff --git a/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml b/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml index 1c68918cee..5c45a7f722 100644 --- a/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml +++ b/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml @@ -327,9 +327,11 @@ ره وندن ورۊ کردن ره وندن ورۊ کردن خوتکار موجاز کردن نشۊوی HTTP نا ٱمن - نیشتنا کانفیگ پروکسی دیندایی - نیشتنا کانفیگ پروکسی نیایی + پروکسی وۊرۊی + پروکسی دری موطمئن بۊ ک نیشتنا هڌس وو جۊرس نی + ای پروکسی پؽش ز هر کانفیگ من ای اشتراک، وری ٱول عوزو زنجیره پروکسی اضاف ابۊ. + ای پروکسی بئڌ ز هر کانفیگ من ای اشتراک، وری آخر عوزو زنجیره پروکسی اضاف ابۊ. فاصله ورۊ کردن نا موئتبر هڌ. حداقل مقدار 15 دؽقه هڌ. ورۊ کردن اشتراک بونکۊ سکویی آزمایش تئخیر TCP (TCPing) @@ -355,7 +357,8 @@ نشقه دامنه سامووا تور جوستن - وا کاما (,) ز یک جوڌا ابۊن؛ یکی ن بزنین: domain، ip یا process + پوی شرطا سئت بیڌه بایڌ هوم زمووی جور ابۊن (AND). قانووݩ یل ز سر و ته وارس ابۊن؛ ٱولی قانووݩ جور وابیڌه و کار ابسته ابۊ. + چنتا مقدار ن وا کاما (,) ز یک جوڌا کۊنین. زفت کردن روفتن سامووا قانووݩ تور جوستن @@ -380,7 +383,8 @@ منپیزن واجۊری کوݩ هونی آزمایش ابۊ… %d کانفیگ هونی آزمایش ابۊ… - مووفق بی: منپیز %dms طۊل کشی + مووفق بی: منپیز %1$s طۊل کشی + %1$d میلی‌ثانیه منپیز و اینترنتن نجوست: %s اینترنت من دسرس نؽ کود خطا: #%d @@ -502,14 +506,6 @@ لف گیری گوزارش روفتن گوزارش ٱووردن قانووݩ - هیچ کانفیگ پسند وابیڌه نؽ - خوندن کانفیگ پسند وابیڌه مووفق نبی - پردازشت کانفیگ پسند وابیڌه مووفق نبی - ساڌن کانفیگ هسته مووفق نبی - ساڌن کانفیگ هسته مووفق نبی: %1$s - ساڌن کانفیگ هسته سی آزمایش سورعت مووفق نبی: %1$s - کانفیگ سفارشی نؽ - ره وستن هسته مووفق نبی ره وستن سرویس واڌاشتن سرویس برگشت diff --git a/V2rayNG/app/src/main/res/values-fa/strings.xml b/V2rayNG/app/src/main/res/values-fa/strings.xml index f16f1d5f0d..c940b16092 100644 --- a/V2rayNG/app/src/main/res/values-fa/strings.xml +++ b/V2rayNG/app/src/main/res/values-fa/strings.xml @@ -327,9 +327,11 @@ فعال کردن به‌روزرسانی فعال سازی به‌روزرسانی خودکار مجاز کردن آدرس HTTP ناامن - نام مستعار پروکسی قبلی - نام مستعار پروکسی بعدی + پروکسی ورودی + پروکسی خروجی لطفاً مطمئن شوید که نام مستعار وجود دارد و منحصر به فرد است + پروکسی انتخاب‌شده پیش از هر پروفایل این اشتراک، به‌عنوان حلقهٔ ورودی زنجیرهٔ پروکسی آن پروفایل اضافه می‌شود. + پروکسی انتخاب‌شده پس از هر پروفایل این اشتراک، به‌عنوان حلقهٔ خروجی زنجیرهٔ پروکسی آن پروفایل اضافه می‌شود. بازه بروزرسانی نامعتبر است. حداقل مقدار 15 دقیقه است. به‌روزرسانی گروه فعلی اشتراک آزمایش تأخیر TCP (TCPing) @@ -355,7 +357,8 @@ استراتژی دامنه تنظیمات مسیریابی - با کاما (,) جدا کنید. یکی از موارد domain، ip یا process را وارد کنید + یک قانون فقط زمانی اعمال می‌شود که همهٔ شرط‌های تطبیق واردشده هم‌زمان برقرار باشند (AND). قوانین از بالا به پایین بررسی می‌شوند و نخستین قانون منطبق اعمال می‌شود. + چند ورودی را با کاما (,) جدا کنید. ذخیره حذف تنظیمات قانون مسیریابی @@ -380,7 +383,8 @@ اتصال را بررسی کنید در حال آزمایش… تست کردن %d کانفیگ… - موفقیت: اتصال %dms طول کشید + اتصال در %1$s با موفقیت برقرار شد + %1$d میلی‌ثانیه آزمایش اتصال ناموفق بود: %s اینترنت در دسترس نیست کد خطا: #%d @@ -502,14 +506,6 @@ کپی گزارش پاک کردن گزارش افزودن قانون مسیریابی - هیچ پیکربندی‌ای انتخاب نشده است - خواندن پیکربندی انتخاب‌شده ناموفق بود - آماده‌سازی پیکربندی انتخاب‌شده ناموفق بود - ایجاد پیکربندی هسته ناموفق بود - ایجاد پیکربندی هسته ناموفق بود: %1$s - ایجاد پیکربندی هسته برای آزمایش سرعت ناموفق بود: %1$s - پیکربندی سفارشی خالی است - راه‌اندازی هسته ناموفق بود شروع سرویس توقف سرویس بازگشت diff --git a/V2rayNG/app/src/main/res/values-ru/strings.xml b/V2rayNG/app/src/main/res/values-ru/strings.xml index c6410fcbbf..0566ed47e8 100644 --- a/V2rayNG/app/src/main/res/values-ru/strings.xml +++ b/V2rayNG/app/src/main/res/values-ru/strings.xml @@ -327,9 +327,11 @@ Включить обновление Использовать автообновление Разрешать незащищённые HTTP-адреса - Предыдущий профиль прокси - Следующий профиль прокси + Входной прокси + Выходной прокси Убедитесь, что профиль с таким названием существует и его название уникально + Выбранный прокси добавляется перед каждым профилем подписки и становится входным звеном цепочки прокси для этого профиля. + Выбранный прокси добавляется после каждого профиля подписки и становится выходным звеном цепочки прокси для этого профиля. Неправильный интервал обновления. Минимум 15 минут. Обновить подписку Проверить TCP-задержку (TCPing) @@ -355,7 +357,8 @@ Доменная стратегия Маршрутизация - Через запятую (,)\nЧто-то одно: домен, IP или процесс + Правило применяется, только если одновременно выполняются все заданные условия сопоставления (AND). Правила проверяются сверху вниз; применяется первое совпавшее правило. + Разделяйте несколько значений запятыми (,). Сохранить Очистить Настройка правил маршрутизации @@ -380,7 +383,8 @@ Проверить соединение Проверка… Проверка профилей (%d)… - Успешно: соединение заняло %d мс + Соединение установлено за %1$s + %1$d мс Ошибка проверки соединения: %s Интернет недоступен Код ошибки: #%d @@ -502,14 +506,6 @@ Копировать журнал Очистить журнал Добавить правило маршрутизации - Конфигурация не выбрана - Не удалось прочитать выбранную конфигурацию - Не удалось подготовить выбранную конфигурацию - Не удалось создать конфигурацию ядра - Не удалось создать конфигурацию ядра: %1$s - Не удалось создать конфигурацию ядра для теста скорости: %1$s - Пользовательская конфигурация пуста - Не удалось запустить ядро Запустить службу Остановить службу Назад diff --git a/V2rayNG/app/src/main/res/values-vi/strings.xml b/V2rayNG/app/src/main/res/values-vi/strings.xml index 1dba9fdd69..0c8189926d 100644 --- a/V2rayNG/app/src/main/res/values-vi/strings.xml +++ b/V2rayNG/app/src/main/res/values-vi/strings.xml @@ -327,9 +327,11 @@ Bật cập nhật Bật tự động cập nhật Cho phép địa chỉ HTTP không an toàn - Tên ghi chú của cấu hình proxy trước đó - Tên ghi chú của cấu hình proxy tiếp theo + Proxy đầu vào + Proxy đầu ra Tên ghi chú của cấu hình phải tồn tại và là duy nhất + Proxy đã chọn được thêm trước mỗi cấu hình trong gói đăng ký, làm mắt xích đầu vào của chuỗi proxy cho cấu hình đó. + Proxy đã chọn được thêm sau mỗi cấu hình trong gói đăng ký, làm mắt xích đầu ra của chuỗi proxy cho cấu hình đó. Khoảng thời gian cập nhật không hợp lệ. Giá trị tối thiểu là 15 phút. Cập nhật gói đăng ký Kiểm tra độ trễ TCP (TCPing) @@ -355,7 +357,8 @@ Chiến lược tên miền (DomainStrategy) Cài đặt định tuyến - Phân cách bằng dấu phẩy (,). Chọn một trong: domain, ip hoặc process + Quy tắc chỉ được áp dụng khi tất cả điều kiện đối sánh đã đặt đều được thỏa mãn cùng lúc (AND). Các quy tắc được kiểm tra từ trên xuống; quy tắc khớp đầu tiên sẽ được áp dụng. + Phân cách nhiều mục bằng dấu phẩy (,). Lưu lại Xoá Cài đặt quy tắc định tuyến @@ -380,7 +383,8 @@ Kiểm tra kết nối Đang kiểm tra kết nối mạng… Đang kiểm tra %d cấu hình… - Kiểm tra thành công: thời gian truy cập Google là %d ms + Kết nối thành công sau %1$s + %1$d mili giây Kiểm tra kết nối thất bại: %s Không thể truy cập Internet Mã lỗi: #%d @@ -502,14 +506,6 @@ Sao chép nhật ký Xóa nhật ký Thêm quy tắc định tuyến - Chưa chọn cấu hình - Không thể đọc cấu hình đã chọn - Không thể chuẩn bị cấu hình đã chọn - Không thể tạo cấu hình lõi - Không thể tạo cấu hình lõi: %1$s - Không thể tạo cấu hình lõi để kiểm tra tốc độ: %1$s - Cấu hình tùy chỉnh đang trống - Không thể khởi động lõi Khởi động dịch vụ Dừng dịch vụ Quay lại diff --git a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml index 0d4e90ac4e..e5009012ff 100644 --- a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml @@ -318,7 +318,7 @@ 删除重复配置 删除无效配置 将配置导出至剪贴板 - 订阅 + 订阅分组 备注 URL(可选) User Agent @@ -330,6 +330,8 @@ 前置代理配置别名 落地代理配置別名 请确保配置别名存在并唯一 + 所选代理会添加到该订阅中每个配置的前面,作为该配置代理链的入口节点。 + 所选代理会添加到该订阅中每个配置的后面,作为该配置代理链的出口节点。 更新间隔无效,最小值为 15 分钟。 更新订阅 测试 TCP 延迟(TCPing) @@ -355,7 +357,8 @@ 域名策略 路由设置 - 用逗号(,)分隔,选择填写 domain、ip 或 process 中的一项 + 一条规则仅在所有已设置的匹配条件同时满足时生效(AND)。规则按从上到下的顺序检查,并应用首条匹配的规则。 + 多个条目请用逗号(,)分隔。 保存 清空 路由规则设置 @@ -380,7 +383,8 @@ "检查网络连接" "测试中…" 测试 %d 个配置中… - "连接成功:延时 %d 毫秒" + "连接成功:延时 %1$s" + %1$d 毫秒 连接测试失败:%s 无法访问互联网 错误码:#%d @@ -502,14 +506,6 @@ 复制日志 清除日志 添加路由规则 - 未选择配置 - 无法读取所选配置 - 无法准备所选配置 - 无法生成核心配置 - 无法生成核心配置:%1$s - 无法生成测速用核心配置:%1$s - 自定义配置为空 - 无法启动核心 启动服务 停止服务 返回 diff --git a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml index 4a11cbf325..a87e893731 100644 --- a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -318,7 +318,7 @@ 刪除重複設定 刪除無效設定 將設定匯出至剪貼簿 - 訂閱 + 訂閱分組 備註 URL(可選) User Agent @@ -330,6 +330,8 @@ 前置代理設定檔别名 落地代理設定檔別名 請確保設定檔別名存在且唯一 + 所選代理會加在此訂閱中每個設定檔之前,作為該設定檔代理鏈的入口節點。 + 所選代理會加在此訂閱中每個設定檔之後,作為該設定檔代理鏈的出口節點。 更新間隔無效,最小值為 15 分鐘。 更新訂閱 測試 TCP 延遲(TCPing) @@ -355,7 +357,8 @@ 網域策略 路由設定 - 用逗號(,)分隔,選擇填寫 domain、ip 或 process 中的一項 + 一條規則只有在所有已設定的比對條件同時符合時才會生效(AND)。規則會由上而下檢查,並套用第一條符合的規則。 + 多個項目請用逗號(,)分隔。 儲存 清除 路由規則設定 @@ -380,7 +383,8 @@ "測試連線能力" "測試中……" 正在測試 %d 個設定… - "成功:%d ms延遲" + "連線成功:延遲 %1$s" + %1$d 毫秒 連線測試失敗:%s "無法使用網際網路" 錯誤碼:#%d @@ -502,14 +506,6 @@ 複製日誌 清除日誌 新增路由規則 - 未選取設定 - 無法讀取所選設定 - 無法準備所選設定 - 無法產生核心設定 - 無法產生核心設定:%1$s - 無法產生測速用核心設定:%1$s - 自訂設定為空 - 無法啟動核心 啟動服務 停止服務 返回 diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index bf8c2ec7fd..de2672d161 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -327,9 +327,11 @@ Enable update Enable automatic update Allow insecure HTTP address - Previous proxy config remarks - Next proxy config remarks + Entry proxy + Exit proxy Make sure the config remarks exist and are unique + The selected proxy is added before every profile in this subscription as the entry link in the proxy chain for that profile. + The selected proxy is added after every profile in this subscription as the exit link in the proxy chain for that profile. Invalid update interval. Minimum is 15 minutes. Update subscription Test TCP delays (TCPing) @@ -355,7 +357,8 @@ Domain strategy Routing Settings - Separated by commas(,), \nchoose domain or ip or process + A rule applies only when every configured matching condition is satisfied (AND). Rules are checked from top to bottom; the first matching rule is applied. + Separate multiple entries with commas (,). Save Clear Routing Rule Settings @@ -380,7 +383,8 @@ Check connectivity Testing… Testing %d configs… - Success: Connection took %dms + Connection succeeded in %1$s + %1$d ms Connection test failed: %s Internet unavailable Error code: #%d @@ -502,14 +506,6 @@ Copy log Clear log Add routing rule - No configuration selected - Failed to read the selected configuration - Failed to prepare the selected configuration - Failed to generate the core configuration - Failed to generate the core configuration: %1$s - Failed to generate the core configuration for the speed test: %1$s - Custom configuration is empty - Failed to start the core Start service Stop service Back