diff --git a/.gitignore b/.gitignore
index 9582b273..b290bba8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,9 @@ V2rayNG/app/build
V2rayNG/build
V2rayNG/local.properties
+# Ignore prebuilt native libraries (built from submodules by compile-hevtun.sh / AndroidLibXrayLite)
+V2rayNG/app/libs
+
# Ignore APK and AAR files
*.apk
*.aar
diff --git a/V2rayNG/app/src/main/AndroidManifest.xml b/V2rayNG/app/src/main/AndroidManifest.xml
index 0238a586..e26a81e1 100644
--- a/V2rayNG/app/src/main/AndroidManifest.xml
+++ b/V2rayNG/app/src/main/AndroidManifest.xml
@@ -209,6 +209,9 @@
+
+)
+
+object TranslatorsParser {
+
+ private val gson = Gson()
+
+ fun parse(json: String): List {
+ if (json.isBlank()) return emptyList()
+
+ return try {
+ val type = object : TypeToken>() {}.type
+ gson.fromJson>(json, type).orEmpty()
+ .filter { it.language.isNotBlank() && it.contributors.isNotEmpty() }
+ .distinctBy { it.language }
+ } catch (e: Exception) {
+ LogUtil.e(AppConfig.TAG, "Failed to parse translators.json", e)
+ emptyList()
+ }
+ }
+}
\ No newline at end of file
diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/AboutActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/AboutActivity.kt
index 41db47ea..a31bc6ce 100644
--- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/AboutActivity.kt
+++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/AboutActivity.kt
@@ -1,5 +1,6 @@
package com.v2ray.ang.ui
+import android.content.Intent
import android.os.Bundle
import android.webkit.WebView
import androidx.compose.foundation.layout.Column
@@ -46,12 +47,20 @@ class AboutActivity : BaseComponentActivity() {
@Composable
override fun ScreenContent() {
- AboutScreen(onBackClick = { finish() })
+ AboutScreen(
+ onBackClick = { finish() },
+ onTranslatorsClick = {
+ startActivity(Intent(this, TranslatorsActivity::class.java))
+ }
+ )
}
}
@Composable
-fun AboutScreen(onBackClick: () -> Unit) {
+fun AboutScreen(
+ onBackClick: () -> Unit,
+ onTranslatorsClick: () -> Unit
+) {
val context = LocalContext.current
var showOssDialog by remember { mutableStateOf(false) }
@@ -84,6 +93,11 @@ fun AboutScreen(onBackClick: () -> Unit) {
title = stringResource(R.string.title_oss_license),
onClick = { showOssDialog = true }
)
+ SettingsMenuItem(
+ icon = painterResource(R.drawable.ic_translate_24dp),
+ title = stringResource(R.string.title_translators),
+ onClick = onTranslatorsClick
+ )
SettingsMenuItem(
icon = painterResource(R.drawable.ic_feedback_24dp),
title = stringResource(R.string.title_pref_feedback),
diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/TranslatorsActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/TranslatorsActivity.kt
new file mode 100644
index 00000000..0f6bf3c9
--- /dev/null
+++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/TranslatorsActivity.kt
@@ -0,0 +1,155 @@
+package com.v2ray.ang.ui
+
+import android.os.Bundle
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.unit.dp
+import com.v2ray.ang.R
+import com.v2ray.ang.dto.TranslatorsCredit
+import com.v2ray.ang.dto.TranslatorsParser
+import com.v2ray.ang.ui.base.BaseComponentActivity
+import com.v2ray.ang.ui.compose.AppTopBar
+import com.v2ray.ang.ui.compose.NavigationBarsSpacer
+import com.v2ray.ang.util.Utils
+
+class TranslatorsActivity : BaseComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ }
+
+ @Composable
+ override fun ScreenContent() {
+ TranslatorsScreen(onBackClick = { finish() })
+ }
+}
+
+@Composable
+fun TranslatorsScreen(onBackClick: () -> Unit) {
+ val context = LocalContext.current
+ val credits = remember {
+ TranslatorsParser.parse(Utils.readTextFromAssets(context, "translators.json"))
+ }
+
+ Scaffold(
+ contentWindowInsets = WindowInsets(0),
+ topBar = {
+ AppTopBar(
+ title = stringResource(R.string.title_translators),
+ onBackClick = onBackClick
+ )
+ }
+ ) { innerPadding ->
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(innerPadding),
+ contentPadding = PaddingValues(16.dp),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ items(credits, key = { it.language }) { credit ->
+ TranslationCreditCard(credit)
+ }
+ item {
+ NavigationBarsSpacer()
+ }
+ }
+ }
+}
+
+@Composable
+private fun TranslationCreditCard(credit: TranslatorsCredit) {
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.surfaceContainerLow
+ )
+ ) {
+ Column {
+ Text(
+ text = credit.language,
+ style = MaterialTheme.typography.titleMedium,
+ modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)
+ )
+ credit.contributors.forEachIndexed { index, contributor ->
+ if (index > 0) {
+ HorizontalDivider(
+ modifier = Modifier.padding(horizontal = 16.dp),
+ color = MaterialTheme.colorScheme.outlineVariant,
+ thickness = 0.5.dp
+ )
+ }
+ ContributorRow(
+ displayName = contributor.displayName ?: contributor.name,
+ linkUrl = contributor.url
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun ContributorRow(displayName: String, linkUrl: String?) {
+ val context = LocalContext.current
+ val isLink = linkUrl != null
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .then(
+ if (isLink) {
+ Modifier.clickable(role = Role.Button) {
+ Utils.openUri(context, linkUrl)
+ }
+ } else {
+ Modifier
+ }
+ )
+ .padding(horizontal = 16.dp, vertical = 12.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ if (isLink) {
+ Icon(
+ painter = painterResource(R.drawable.ic_github_24dp),
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.primary,
+ modifier = Modifier.size(20.dp)
+ )
+ }
+ Text(
+ text = displayName,
+ style = MaterialTheme.typography.bodyLarge,
+ color = if (isLink) {
+ MaterialTheme.colorScheme.primary
+ } else {
+ MaterialTheme.colorScheme.onSurface
+ },
+ modifier = Modifier.padding(start = if (isLink) 12.dp else 0.dp)
+ )
+ }
+}
\ No newline at end of file
diff --git a/V2rayNG/app/src/main/res/drawable/ic_github_24dp.xml b/V2rayNG/app/src/main/res/drawable/ic_github_24dp.xml
new file mode 100644
index 00000000..70f18878
--- /dev/null
+++ b/V2rayNG/app/src/main/res/drawable/ic_github_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/V2rayNG/app/src/main/res/drawable/ic_translate_24dp.xml b/V2rayNG/app/src/main/res/drawable/ic_translate_24dp.xml
new file mode 100644
index 00000000..bfa3f401
--- /dev/null
+++ b/V2rayNG/app/src/main/res/drawable/ic_translate_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/V2rayNG/app/src/main/res/values-ar/strings.xml b/V2rayNG/app/src/main/res/values-ar/strings.xml
index 3e32f704..378b71ae 100644
--- a/V2rayNG/app/src/main/res/values-ar/strings.xml
+++ b/V2rayNG/app/src/main/res/values-ar/strings.xml
@@ -283,6 +283,7 @@
حول التطبيق
الكود المصدري
تراخيص المصادر المفتوحة
+ المترجمون
قناة Telegram
ترويج
diff --git a/V2rayNG/app/src/main/res/values-bn/strings.xml b/V2rayNG/app/src/main/res/values-bn/strings.xml
index f604d716..78effa02 100644
--- a/V2rayNG/app/src/main/res/values-bn/strings.xml
+++ b/V2rayNG/app/src/main/res/values-bn/strings.xml
@@ -283,6 +283,7 @@
সম্পর্কিত
সোর্স কোড
ওপেন সোর্স লাইসেন্স
+ অনুবাদকগণ
টেলিগ্রাম চ্যানেল
প্রচার
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 d48b0dbc..f85a9418 100644
--- a/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml
+++ b/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml
@@ -283,6 +283,7 @@
زبار
کود بونچک
موجوزا کود بونچک
+ ترجومهگرا
تورگه تلگرام
تبلیغات
diff --git a/V2rayNG/app/src/main/res/values-fa/strings.xml b/V2rayNG/app/src/main/res/values-fa/strings.xml
index bf4b721c..116846f3 100644
--- a/V2rayNG/app/src/main/res/values-fa/strings.xml
+++ b/V2rayNG/app/src/main/res/values-fa/strings.xml
@@ -283,6 +283,7 @@
درباره
کد منبع
مجوز های منبع باز
+ مترجمان
کانال تلگرام
تبلیغات
diff --git a/V2rayNG/app/src/main/res/values-ru/strings.xml b/V2rayNG/app/src/main/res/values-ru/strings.xml
index 4fadf364..ee93da42 100644
--- a/V2rayNG/app/src/main/res/values-ru/strings.xml
+++ b/V2rayNG/app/src/main/res/values-ru/strings.xml
@@ -283,6 +283,7 @@
О приложении
Исходный код
Лицензии открытого исходного кода
+ Переводчики
Telegram-канал
Предложения
diff --git a/V2rayNG/app/src/main/res/values-vi/strings.xml b/V2rayNG/app/src/main/res/values-vi/strings.xml
index fc0af8ef..4e912e42 100644
--- a/V2rayNG/app/src/main/res/values-vi/strings.xml
+++ b/V2rayNG/app/src/main/res/values-vi/strings.xml
@@ -283,6 +283,7 @@
Giới thiệu
Mã nguồn
Giấy phép mã nguồn mở
+ Người dịch
Kênh Telegram
Ưu đã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 1c0c6918..0ceb0b88 100644
--- a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml
+++ b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml
@@ -283,6 +283,7 @@
关于
源代码
开源许可证
+ 翻译贡献者
Telegram 频道
推广
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 344253f7..cb7bae55 100644
--- a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml
+++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml
@@ -283,6 +283,7 @@
關於
原始碼
開放原始碼授權
+ 翻譯貢獻者
Telegram 頻道
推廣
diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml
index 6834e9dd..b96ce5f2 100644
--- a/V2rayNG/app/src/main/res/values/strings.xml
+++ b/V2rayNG/app/src/main/res/values/strings.xml
@@ -283,6 +283,7 @@
About
Source code
Open-source licenses
+ Translators
Telegram channel
Promotion