feat: 增强多语言支持与本地化功能
- 新增多语言问题修复计划文档,详细描述了多语言支持的现状与解决方案。 - 在LocalizationManager中启用全局本地化方法,替换多个视图中的NSLocalizedString调用为LocalizedString。 - 更新MainFeature以确保在MeView标签页时正确加载用户数据。 - 在多个视图中添加语言切换测试区域,确保文本实时更新。 - 修复MeView显示问题,确保用户信息和动态内容正确加载。
This commit is contained in:
@@ -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 到设置页,带入当前用户信息
|
||||
|
@@ -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 {
|
||||
// 这里可以添加动态更新逻辑
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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")
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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))
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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") {
|
||||
|
@@ -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 // 显示邮箱登录界面
|
||||
}
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user