语言切换

This commit is contained in:
2026-05-27 09:36:56 +08:00
parent 298b06d456
commit ac906c901b
13 changed files with 120 additions and 77 deletions

View File

@@ -6,7 +6,6 @@ import bean.LoginBean
import com.blankj.utilcode.util.Utils import com.blankj.utilcode.util.Utils
import com.example.defenseapplication.bean.BaseInfo import com.example.defenseapplication.bean.BaseInfo
import com.example.defenseapplication.utils.ApiConfig import com.example.defenseapplication.utils.ApiConfig
import com.example.defenseapplication.utils.LanguageHeaderManager
import com.example.defenseapplication.utils.LanguageUtil import com.example.defenseapplication.utils.LanguageUtil
import com.example.defenseapplication.utils.UserinfoUtil import com.example.defenseapplication.utils.UserinfoUtil
import com.google.gson.Gson import com.google.gson.Gson
@@ -21,7 +20,7 @@ class MyApplication : Application() {
if (token != null) { if (token != null) {
ApiConfig.authToken = token ApiConfig.authToken = token
}; };
LanguageHeaderManager.init(this) LanguageUtil.init(this)
} }
override fun attachBaseContext(base: Context) { override fun attachBaseContext(base: Context) {

View File

@@ -4,6 +4,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.example.defenseapplication.R
import com.example.defenseapplication.databinding.ItemLinkedUserBinding import com.example.defenseapplication.databinding.ItemLinkedUserBinding
data class LinkedUser( data class LinkedUser(
@@ -54,10 +55,11 @@ class LinkedUserAdapter(
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: LinkedUser, position: Int) { fun bind(item: LinkedUser, position: Int) {
val context = binding.root.context
binding.tvUserName.text = item.name binding.tvUserName.text = item.name
binding.tvTime.text = item.time binding.tvTime.text = item.time
binding.tvPhone.text = "手机号码:${item.phone}" binding.tvPhone.text = context.getString(R.string.linked_user_phone_label, item.phone)
binding.tvIdCard.text = "身份证号:${item.idCard}" binding.tvIdCard.text = context.getString(R.string.linked_user_idcard_label, item.idCard)
binding.root.setOnClickListener { binding.root.setOnClickListener {
onItemClick(item, position) onItemClick(item, position)

View File

@@ -1,13 +1,20 @@
package com.example.defenseapplication.base package com.example.defenseapplication.base
import android.content.Context
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.example.defenseapplication.utils.ActivityManager import com.example.defenseapplication.utils.ActivityManager
import com.example.defenseapplication.utils.LanguageUtil
open class BaseActivity : AppCompatActivity() { open class BaseActivity : AppCompatActivity() {
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(LanguageUtil.attachBaseContext(newBase))
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
LanguageUtil.init(this)
ActivityManager.addActivity(this) ActivityManager.addActivity(this)
} }

View File

@@ -1,18 +1,17 @@
package com.example.defenseapplication.home package com.example.defenseapplication.home
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.example.defenseapplication.R import com.example.defenseapplication.R
import com.example.defenseapplication.base.BaseActivity
import com.example.defenseapplication.databinding.HomeActivityBinding import com.example.defenseapplication.databinding.HomeActivityBinding
import com.example.defenseapplication.databinding.HomeMenuFragmentBinding import com.example.defenseapplication.databinding.HomeMenuFragmentBinding
import com.gyf.immersionbar.ImmersionBar import com.gyf.immersionbar.ImmersionBar
class HomeActivity : AppCompatActivity() { class HomeActivity : BaseActivity() {
// ViewBinding 核心对象 // ViewBinding 核心对象
private lateinit var binding: HomeActivityBinding private lateinit var binding: HomeActivityBinding

View File

@@ -16,11 +16,14 @@ import com.example.defenseapplication.liveVideo.LinkedUserActivity
import com.example.defenseapplication.personal.FaceManagementActivity import com.example.defenseapplication.personal.FaceManagementActivity
import com.example.defenseapplication.personal.PasswordManagementActivity import com.example.defenseapplication.personal.PasswordManagementActivity
import com.example.defenseapplication.personal.PersonalProfileActivity import com.example.defenseapplication.personal.PersonalProfileActivity
import com.example.defenseapplication.utils.AppLanguage
import com.example.defenseapplication.utils.LanguageUtil import com.example.defenseapplication.utils.LanguageUtil
import com.example.defenseapplication.utils.RetrofitNetwork import com.example.defenseapplication.utils.RetrofitNetwork
import com.example.defenseapplication.utils.UserinfoUtil import com.example.defenseapplication.utils.UserinfoUtil
import com.example.defenseapplication.utils.ActivityManager
import com.example.defenseapplication.view.FamilySwitchDialog import com.example.defenseapplication.view.FamilySwitchDialog
import com.example.defenseapplication.view.LanguageDialog import com.example.defenseapplication.view.LanguageDialog
import com.example.defenseapplication.home.HomeActivity
import com.gyf.immersionbar.ImmersionBar import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -155,18 +158,23 @@ class PersonalFragment : Fragment() {
* 更新当前语言显示 * 更新当前语言显示
*/ */
private fun updateCurrentLanguageDisplay() { private fun updateCurrentLanguageDisplay() {
val currentLang = LanguageUtil.getCurrentLanguage(requireContext()) binding.tvCurrentLanguage.text = LanguageUtil.currentLanguage.let {
binding.tvCurrentLanguage.text = LanguageUtil.getLanguageDisplayName(currentLang) if (it == AppLanguage.SIMPLIFIED_CHINESE) "中文" else "English"
}
} }
/** /**
* 显示语言选择对话框 * 显示语言选择对话框
*/ */
private fun showLanguageDialog() { private fun showLanguageDialog() {
val dialog = LanguageDialog(requireContext()) { language -> val dialog = LanguageDialog(requireContext()) { appLanguage ->
LanguageUtil.switchLanguage(requireContext(), language) LanguageUtil.switchLanguage(requireContext(), appLanguage)
// 刷新当前页面以应用新语言 // 关闭所有 Activity重启到首页
requireActivity().recreate() ActivityManager.finishAllActivities()
val intent = Intent(requireContext(), HomeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
requireActivity().finish()
} }
dialog.show() dialog.show()
} }

View File

@@ -13,7 +13,6 @@ import com.example.defenseapplication.utils.DeviceIdEvent
import com.example.defenseapplication.utils.LanguageUtil import com.example.defenseapplication.utils.LanguageUtil
import com.example.defenseapplication.utils.RetrofitNetwork import com.example.defenseapplication.utils.RetrofitNetwork
import com.example.defenseapplication.view.CustomDialogFragmentText import com.example.defenseapplication.view.CustomDialogFragmentText
import com.example.defenseapplication.view.LanguageDialog
import com.example.defenseapplication.view.RecognitionModeDialog import com.example.defenseapplication.view.RecognitionModeDialog
import com.gyf.immersionbar.ImmersionBar import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope

View File

@@ -162,11 +162,10 @@ object ApiConfig {
fun createHeadersInterceptor() = Interceptor { chain -> fun createHeadersInterceptor() = Interceptor { chain ->
val requestBuilder = chain.request().newBuilder() val requestBuilder = chain.request().newBuilder()
val token = authToken val token = authToken
val langHeader = LanguageHeaderManager.getLanguageHeaderValue()
// 添加必要的请求头 // 添加必要的请求头
requestBuilder.header("clientid", "428a8310cd442757ae699df5d894f051") requestBuilder.header("clientid", "428a8310cd442757ae699df5d894f051")
requestBuilder.header("content-language", langHeader) requestBuilder.header("content-language", LanguageUtil.getLanguageHeaderValue())
// 如果有 token添加 Authorization 头 // 如果有 token添加 Authorization 头
if (token.isNotBlank()) { if (token.isNotBlank()) {

View File

@@ -1,37 +1,13 @@
package com.example.defenseapplication.utils package com.example.defenseapplication.utils
import android.content.Context import android.content.Context
import androidx.core.content.edit
enum class AppLanguage(val code: String) {
ENGLISH("en_US"),
SIMPLIFIED_CHINESE("zh_CN"),
TRADITIONAL_CHINESE("zh_TW")
}
/**
* HTTP 请求头的语言值管理,统一由 LanguageUtil 提供
* @deprecated Use LanguageUtil.getLanguageHeaderValue() directly
*/
object LanguageHeaderManager { object LanguageHeaderManager {
private const val PREF_NAME = "app_lang_pref"
private const val KEY_LANGUAGE = "selected_language"
private var currentLanguage: AppLanguage = AppLanguage.ENGLISH @Deprecated("Use LanguageUtil.getLanguageHeaderValue() instead", ReplaceWith("LanguageUtil.getLanguageHeaderValue()"))
fun getLanguageHeaderValue(): String = LanguageUtil.getLanguageHeaderValue()
fun init(context: Context) {
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
val langCode = prefs.getString(KEY_LANGUAGE, AppLanguage.ENGLISH.code)
currentLanguage = AppLanguage.values().find { it.code == langCode } ?: AppLanguage.ENGLISH
}
fun getCurrentLanguage(): AppLanguage = currentLanguage
fun switchLanguage(context: Context, language: AppLanguage) {
currentLanguage = language
// 持久化
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit {
putString(KEY_LANGUAGE, language.code)
apply()
}
// 可选:更新应用 Locale、重启 Activity 等
}
fun getLanguageHeaderValue(): String = currentLanguage.code
} }

View File

@@ -3,77 +3,128 @@ package com.example.defenseapplication.utils
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources
import android.os.Build import android.os.Build
import android.os.LocaleList import android.os.LocaleList
import java.util.Locale import java.util.Locale
enum class AppLanguage(val code: String, val locale: Locale) {
ENGLISH("en_US", Locale.US),
SIMPLIFIED_CHINESE("zh_CN", Locale.SIMPLIFIED_CHINESE);
companion object {
fun fromCode(code: String): AppLanguage {
return values().find { it.code == code } ?: SIMPLIFIED_CHINESE
}
}
}
object LanguageUtil { object LanguageUtil {
private const val PREF_NAME = "language_pref" private const val PREF_NAME = "language_pref"
private const val KEY_LANGUAGE = "selected_language" private const val KEY_LANGUAGE = "selected_language"
const val LANGUAGE_CHINESE = "zh" @JvmStatic
const val LANGUAGE_ENGLISH = "en" var currentLanguage: AppLanguage = AppLanguage.SIMPLIFIED_CHINESE
private set
/** /**
* 切换语言 * 初始化语言,从持久化中恢复
* @param context 上下文 */
* @param language 语言代码: "zh" 中文, "en" 英文 fun init(context: Context) {
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
var code = prefs.getString(KEY_LANGUAGE, null)
if (code == null) {
// 兼容旧数据:尝试从 app_lang_pref 读取LanguageHeaderManager 旧存储路径)
val oldPrefs = context.getSharedPreferences("app_lang_pref", Context.MODE_PRIVATE)
code = oldPrefs.getString("selected_language", AppLanguage.SIMPLIFIED_CHINESE.code)
?: AppLanguage.SIMPLIFIED_CHINESE.code
// 迁移到新存储路径
prefs.edit().putString(KEY_LANGUAGE, code).apply()
}
currentLanguage = AppLanguage.fromCode(code)
}
/**
* 切换语言,同时更新 app locale 和 HTTP header
* @param language AppLanguage.ENGLISH 或 AppLanguage.SIMPLIFIED_CHINESE
*/
fun switchLanguage(context: Context, language: AppLanguage) {
currentLanguage = language
saveLanguage(context, language.code)
}
/**
* 兼容旧代码:使用字符串 "zh" / "en" 切换
*/ */
fun switchLanguage(context: Context, language: String) { fun switchLanguage(context: Context, language: String) {
saveLanguage(context, language) val appLang = when (language) {
"zh" -> AppLanguage.SIMPLIFIED_CHINESE
else -> AppLanguage.ENGLISH
}
switchLanguage(context, appLang)
} }
/** /**
* 获取当前设置的语言 * 获取当前设置的语言代码 "zh" / "en"
*/ */
fun getCurrentLanguage(context: Context): String { fun getCurrentLanguage(context: Context): String {
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) return currentLanguage.name.lowercase().let {
return prefs.getString(KEY_LANGUAGE, LANGUAGE_CHINESE) ?: LANGUAGE_CHINESE if (it.contains("chinese")) "zh" else "en"
} }
}
/**
* 获取 HTTP header 的语言值 "en_US" / "zh_CN"
*/
fun getLanguageHeaderValue(): String = currentLanguage.code
/** /**
* 保存语言设置 * 保存语言设置
*/ */
private fun saveLanguage(context: Context, language: String) { private fun saveLanguage(context: Context, languageCode: String) {
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
prefs.edit().putString(KEY_LANGUAGE, language).apply() prefs.edit().putString(KEY_LANGUAGE, languageCode).apply()
} }
/** /**
* 判断当前是否是中文 * 判断当前是否是中文
*/ */
fun isChinese(context: Context): Boolean { fun isChinese(context: Context): Boolean {
return getCurrentLanguage(context) == LANGUAGE_CHINESE return currentLanguage == AppLanguage.SIMPLIFIED_CHINESE
} }
/** /**
* 获取本地化的 Context用于在应用内切换语言 * 获取本地化的 Context在 attachBaseContext 中调用
* 在 attachBaseContext 中调用
*/ */
fun attachBaseContext(context: Context): Context { fun attachBaseContext(context: Context): Context {
val language = getCurrentLanguage(context) val locale = getSavedLocale(context)
return createLocalizedContext(context, language) return createLocalizedContext(context, locale)
}
/**
* 读取已保存的语言 localeattachBaseContext 中使用,此时 currentLanguage 可能尚未初始化)
*/
private fun getSavedLocale(context: Context): Locale {
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
val code = prefs.getString(KEY_LANGUAGE, AppLanguage.SIMPLIFIED_CHINESE.code)
?: AppLanguage.SIMPLIFIED_CHINESE.code
return AppLanguage.fromCode(code).locale
} }
/** /**
* 创建本地化的 Context * 创建本地化的 Context
*/ */
private fun createLocalizedContext(context: Context, language: String): Context { private fun createLocalizedContext(context: Context, locale: Locale): Context {
val locale = Locale(language)
Locale.setDefault(locale) Locale.setDefault(locale)
val resources = context.resources val resources = context.resources
val configuration = resources.configuration val configuration = resources.configuration
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// Android 7.0 及以上
val localeList = LocaleList(locale) val localeList = LocaleList(locale)
configuration.setLocales(localeList) configuration.setLocales(localeList)
context.createConfigurationContext(configuration) context.createConfigurationContext(configuration)
} else { } else {
// Android 7.0 以下
configuration.locale = locale configuration.locale = locale
resources.updateConfiguration(configuration, resources.displayMetrics) resources.updateConfiguration(configuration, resources.displayMetrics)
context context
@@ -84,8 +135,7 @@ object LanguageUtil {
* 更新应用语言(用于在 Activity 中动态切换) * 更新应用语言(用于在 Activity 中动态切换)
*/ */
fun updateResources(context: Context): Context { fun updateResources(context: Context): Context {
val language = getCurrentLanguage(context) val locale = currentLanguage.locale
val locale = Locale(language)
Locale.setDefault(locale) Locale.setDefault(locale)
val resources = context.resources val resources = context.resources
@@ -107,8 +157,8 @@ object LanguageUtil {
*/ */
fun getLanguageDisplayName(language: String): String { fun getLanguageDisplayName(language: String): String {
return when (language) { return when (language) {
LANGUAGE_CHINESE -> "中文" "zh" -> "中文"
LANGUAGE_ENGLISH -> "English" "en" -> "English"
else -> "中文" else -> "中文"
} }
} }

View File

@@ -9,6 +9,7 @@ import android.view.WindowManager
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import com.example.defenseapplication.R import com.example.defenseapplication.R
import com.example.defenseapplication.utils.AppLanguage
import com.example.defenseapplication.utils.LanguageUtil import com.example.defenseapplication.utils.LanguageUtil
/** /**
@@ -16,7 +17,7 @@ import com.example.defenseapplication.utils.LanguageUtil
*/ */
class LanguageDialog( class LanguageDialog(
context: Context, context: Context,
private val onLanguageSelected: (String) -> Unit private val onLanguageSelected: (AppLanguage) -> Unit
) : Dialog(context, R.style.BottomDialogStyle) { ) : Dialog(context, R.style.BottomDialogStyle) {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -43,22 +44,21 @@ class LanguageDialog(
val tvEnglish = findViewById<TextView>(R.id.tvEnglish) val tvEnglish = findViewById<TextView>(R.id.tvEnglish)
val tvCancel = findViewById<TextView>(R.id.tvCancel) val tvCancel = findViewById<TextView>(R.id.tvCancel)
val currentLang = LanguageUtil.getCurrentLanguage(context) val currentLang = LanguageUtil.currentLanguage
// 根据当前语言高亮显示 if (currentLang == AppLanguage.SIMPLIFIED_CHINESE) {
if (currentLang == LanguageUtil.LANGUAGE_CHINESE) {
tvChinese.setTextColor(context.getColor(R.color.green)) tvChinese.setTextColor(context.getColor(R.color.green))
} else { } else {
tvEnglish.setTextColor(context.getColor(R.color.green)) tvEnglish.setTextColor(context.getColor(R.color.green))
} }
llChinese.setOnClickListener { llChinese.setOnClickListener {
onLanguageSelected(LanguageUtil.LANGUAGE_CHINESE) onLanguageSelected(AppLanguage.SIMPLIFIED_CHINESE)
dismiss() dismiss()
} }
llEnglish.setOnClickListener { llEnglish.setOnClickListener {
onLanguageSelected(LanguageUtil.LANGUAGE_ENGLISH) onLanguageSelected(AppLanguage.ENGLISH)
dismiss() dismiss()
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -470,4 +470,6 @@
<string name="uploading">上传中...</string> <string name="uploading">上传中...</string>
<string name="upload_failed">上传失败</string> <string name="upload_failed">上传失败</string>
<string name="max_add_11_users">最多添加11个用户</string> <string name="max_add_11_users">最多添加11个用户</string>
<string name="linked_user_phone_label">手机号码:%s</string>
<string name="linked_user_idcard_label">身份证号:%s</string>
</resources> </resources>

View File

@@ -474,4 +474,6 @@
<string name="select_date">Select Date</string> <string name="select_date">Select Date</string>
<string name="select_time">Select Time</string> <string name="select_time">Select Time</string>
<string name="max_add_11_users">A maximum of 11 users can be added</string> <string name="max_add_11_users">A maximum of 11 users can be added</string>
<string name="linked_user_phone_label">Phone: %s</string>
<string name="linked_user_idcard_label">ID: %s</string>
</resources> </resources>