feat: 增强多语言支持与本地化功能

- 新增多语言问题修复计划文档,详细描述了多语言支持的现状与解决方案。
- 在LocalizationManager中启用全局本地化方法,替换多个视图中的NSLocalizedString调用为LocalizedString。
- 更新MainFeature以确保在MeView标签页时正确加载用户数据。
- 在多个视图中添加语言切换测试区域,确保文本实时更新。
- 修复MeView显示问题,确保用户信息和动态内容正确加载。
This commit is contained in:
edwinQQQ
2025-07-28 18:28:24 +08:00
parent 6a9dd3fe52
commit 30c3e530fb
10 changed files with 166 additions and 44 deletions

View File

@@ -74,6 +74,14 @@ struct MainFeature {
return .none
case let .accountModelLoaded(accountModel):
state.accountModel = accountModel
// MeView uid
if state.selectedTab == .other, let uidStr = accountModel?.uid, let uid = Int(uidStr), uid > 0 {
if state.me.uid != uid {
state.me.uid = uid
state.me.isFirstLoad = true
}
return .send(.me(.onAppear))
}
return .none
case .me(.settingButtonTapped):
// push

View File

@@ -114,26 +114,51 @@ class LocalizationManager: ObservableObject {
}
// MARK: - SwiftUI Extensions
// extension View {
// ///
// /// - Parameter key: key
// /// - Returns:
// @MainActor
// func localized(_ key: String) -> some View {
// self.modifier(LocalizedTextModifier(key: key))
// }
// }
extension View {
///
/// - Parameter key: key
/// - Returns:
@MainActor
func localized(_ key: String) -> some View {
self.modifier(LocalizedTextModifier(key: key))
}
}
// MARK: - 便
// extension String {
// ///
// @MainActor
// var localized: String {
// return LocalizationManager.shared.localizedString(self)
// }
// ///
// @MainActor
// func localized(arguments: CVarArg...) -> String {
// return LocalizationManager.shared.localizedString(self, arguments: arguments)
// }
// }
extension String {
///
@MainActor
var localized: String {
return LocalizationManager.shared.localizedString(self)
}
///
@MainActor
func localized(arguments: CVarArg...) -> String {
return LocalizationManager.shared.localizedString(self, arguments: arguments)
}
}
// MARK: -
///
/// 使 LocalizationManager
/// - Parameters:
/// - key: key
/// - comment: NSLocalizedString
/// - Returns:
@MainActor
func LocalizedString(_ key: String, comment: String = "") -> String {
return LocalizationManager.shared.localizedString(key)
}
// MARK: - LocalizedTextModifier
///
struct LocalizedTextModifier: ViewModifier {
let key: String
func body(content: Content) -> some View {
content
.onAppear {
//
}
}
}

View File

@@ -38,20 +38,20 @@ struct UserAgreementView: View {
// MARK: - Private Methods
private func createAttributedText() -> AttributedString {
var attributedString = AttributedString(NSLocalizedString("login.agreement_policy", comment: ""))
var attributedString = AttributedString(LocalizedString("login.agreement_policy", comment: ""))
//
attributedString.foregroundColor = Color(hex: 0x666666)
// ""
if let userServiceRange = attributedString.range(of: NSLocalizedString("login.agreement", comment: "")) {
if let userServiceRange = attributedString.range(of: LocalizedString("login.agreement", comment: "")) {
attributedString[userServiceRange].foregroundColor = Color(hex: 0x8A4FFF)
attributedString[userServiceRange].underlineStyle = .single
attributedString[userServiceRange].link = URL(string: "user-service-agreement")
}
// ""
if let privacyPolicyRange = attributedString.range(of: NSLocalizedString("login.policy", comment: "")) {
if let privacyPolicyRange = attributedString.range(of: LocalizedString("login.policy", comment: "")) {
attributedString[privacyPolicyRange].foregroundColor = Color(hex: 0x8A4FFF)
attributedString[privacyPolicyRange].underlineStyle = .single
attributedString[privacyPolicyRange].link = URL(string: "privacy-policy")

View File

@@ -27,7 +27,7 @@ struct EMailLoginView: View {
} else if codeCountdown > 0 {
return "\(codeCountdown)S"
} else {
return NSLocalizedString("email_login.get_code", comment: "")
return LocalizedString("email_login.get_code", comment: "")
}
}
private var isCodeButtonEnabled: Bool {
@@ -147,7 +147,7 @@ private struct LoginContentView: View {
.padding(.horizontal, 16)
.padding(.top, 8)
Spacer().frame(height: 60)
Text(NSLocalizedString("email_login.title", comment: ""))
Text(LocalizedString("email_login.title", comment: ""))
.font(.system(size: 28, weight: .medium))
.foregroundColor(.white)
.padding(.bottom, 80)
@@ -235,7 +235,7 @@ private struct LoginContentView: View {
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.scaleEffect(0.8)
}
Text(store.isLoading ? NSLocalizedString("email_login.logging_in", comment: "") : NSLocalizedString("email_login.login_button", comment: ""))
Text(store.isLoading ? LocalizedString("email_login.logging_in", comment: "") : LocalizedString("email_login.login_button", comment: ""))
.font(.system(size: 18, weight: .semibold))
.foregroundColor(.white)
}

View File

@@ -21,7 +21,7 @@ struct TopBarView: View {
ZStack {
HStack {
Spacer(minLength: 0)
Text(NSLocalizedString("feedList.title", comment: "Enjoy your Life Time"))
Text(LocalizedString("feedList.title", comment: "Enjoy your Life Time"))
.font(.system(size: 22, weight: .semibold))
.foregroundColor(.white)
.frame(maxWidth: .infinity, alignment: .center)
@@ -66,7 +66,7 @@ struct ErrorView: View {
// MARK: - EmptyView
struct EmptyView: View {
var body: some View {
Text(NSLocalizedString("feedList.empty", comment: "暂无动态"))
Text(LocalizedString("feedList.empty", comment: "暂无动态"))
.font(.system(size: 16))
.foregroundColor(.white.opacity(0.7))
.padding(.top, 20)
@@ -232,7 +232,7 @@ struct FeedListView: View {
.frame(width: 56, height: 41)
.padding(.top, 16)
Text(NSLocalizedString("feedList.slogan", comment: "The disease is like a cruel ruler,\nand time is our most precious treasure.\nEvery moment we live is a victory\nagainst the inevitable."))
Text(LocalizedString("feedList.slogan", comment: "The disease is like a cruel ruler,\nand time is our most precious treasure.\nEvery moment we live is a victory\nagainst the inevitable."))
.font(.system(size: 16))
.multilineTextAlignment(.leading)
.foregroundColor(.white.opacity(0.9))

View File

@@ -52,7 +52,7 @@ struct IDLoginView: View {
.frame(height: 60)
//
Text(NSLocalizedString("id_login.title", comment: ""))
Text(LocalizedString("id_login.title", comment: ""))
.font(.system(size: 28, weight: .medium))
.foregroundColor(.white)
.padding(.bottom, 80)
@@ -71,7 +71,7 @@ struct IDLoginView: View {
TextField("", text: $userID) // 使SwiftUI
.placeholder(when: userID.isEmpty) {
Text(NSLocalizedString("placeholder.enter_id", comment: ""))
Text(LocalizedString("placeholder.enter_id", comment: ""))
.foregroundColor(.white.opacity(0.6))
}
.foregroundColor(.white)
@@ -94,7 +94,7 @@ struct IDLoginView: View {
if isPasswordVisible {
TextField("", text: $password) // 使SwiftUI
.placeholder(when: password.isEmpty) {
Text(NSLocalizedString("placeholder.enter_password", comment: ""))
Text(LocalizedString("placeholder.enter_password", comment: ""))
.foregroundColor(.white.opacity(0.6))
}
.foregroundColor(.white)
@@ -102,7 +102,7 @@ struct IDLoginView: View {
} else {
SecureField("", text: $password) // 使SwiftUI
.placeholder(when: password.isEmpty) {
Text(NSLocalizedString("placeholder.enter_password", comment: ""))
Text(LocalizedString("placeholder.enter_password", comment: ""))
.foregroundColor(.white.opacity(0.6))
}
.foregroundColor(.white)
@@ -126,7 +126,7 @@ struct IDLoginView: View {
Button(action: {
showRecoverPassword = true
}) {
Text(NSLocalizedString("id_login.forgot_password", comment: ""))
Text(LocalizedString("id_login.forgot_password", comment: ""))
.font(.system(size: 14))
.foregroundColor(.white.opacity(0.8))
}
@@ -156,7 +156,7 @@ struct IDLoginView: View {
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.scaleEffect(0.8)
}
Text(store.isLoading ? NSLocalizedString("id_login.logging_in", comment: "") : NSLocalizedString("id_login.login_button", comment: ""))
Text(store.isLoading ? LocalizedString("id_login.logging_in", comment: "") : LocalizedString("id_login.login_button", comment: ""))
.font(.system(size: 18, weight: .semibold))
.foregroundColor(.white)
}

View File

@@ -48,6 +48,32 @@ struct LanguageSettingsView: View {
.foregroundColor(.secondary)
}
//
Section {
VStack(alignment: .leading, spacing: 8) {
Text("语言切换测试")
.font(.headline)
.foregroundColor(.primary)
Text("应用标题: \(LocalizedString("login.app_title", comment: ""))")
.font(.caption)
.foregroundColor(.secondary)
Text("登录按钮: \(LocalizedString("login.id_login", comment: ""))")
.font(.caption)
.foregroundColor(.secondary)
Text("当前语言代码: \(localizationManager.currentLanguage.rawValue)")
.font(.caption)
.foregroundColor(.secondary)
}
.padding(.vertical, 4)
} header: {
Text("测试区域")
.font(.caption)
.foregroundColor(.secondary)
}
#if DEBUG
Section("调试功能") {
Button("测试腾讯云 COS Token") {

View File

@@ -48,7 +48,7 @@ struct LoginView: View {
)
// E-PARTI "top"20
HStack {
Text(NSLocalizedString("login.app_title", comment: ""))
Text(LocalizedString("login.app_title", comment: ""))
.font(FontManager.adaptedFont(.bayonRegular, designSize: 56, for: geometry.size.width))
.foregroundColor(.white)
.padding(.leading, 20)
@@ -82,7 +82,7 @@ struct LoginView: View {
LoginButton(
iconName: "person.circle.fill",
iconColor: .green,
title: NSLocalizedString("login.id_login", comment: "")
title: LocalizedString("login.id_login", comment: "")
) {
showIDLogin = true // SwiftUI
}
@@ -90,7 +90,7 @@ struct LoginView: View {
LoginButton(
iconName: "envelope.fill",
iconColor: .blue,
title: NSLocalizedString("login.email_login", comment: "")
title: LocalizedString("login.email_login", comment: "")
) {
showEmailLogin = true //
}

View File

@@ -107,10 +107,10 @@ struct MeView: View {
Image(systemName: "person.fill").font(.system(size: 40)).foregroundColor(.white)
.frame(width: 90, height: 90)
}
Text(userInfo.nick ?? "用户昵称")
Text(userInfo.nick ?? LocalizedString("me.nickname", comment: "用户昵称"))
.font(.system(size: 18, weight: .medium))
.foregroundColor(.white)
Text("ID: \(userInfo.uid ?? 0)")
Text(LocalizedString("me.id", comment: "ID: %@").localized(arguments: String(userInfo.uid ?? 0)))
.font(.system(size: 14))
.foregroundColor(.white.opacity(0.7))
}
@@ -125,7 +125,7 @@ struct MeView: View {
@ViewBuilder
private func momentsSection(viewStore: ViewStoreOf<MeFeature>) -> some View {
if viewStore.isLoadingMoments && viewStore.moments.isEmpty {
ProgressView("加载中...")
ProgressView(LocalizedString("feed.loadingMore", comment: "加载中..."))
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else if let error = viewStore.momentsError {
VStack(spacing: 16) {
@@ -134,7 +134,7 @@ struct MeView: View {
.foregroundColor(.yellow)
Text(error)
.foregroundColor(.red)
Button("重试") {
Button(LocalizedString("feed.retry", comment: "重试")) {
viewStore.send(.onAppear)
}
}
@@ -144,7 +144,7 @@ struct MeView: View {
Image(systemName: "tray")
.font(.system(size: 40))
.foregroundColor(.gray)
Text("暂无动态")
Text(LocalizedString("feed.empty", comment: "暂无动态"))
.foregroundColor(.white.opacity(0.8))
}
.frame(maxWidth: .infinity, maxHeight: .infinity)