From 30c3e530fb28d95b4bd3f9c7c80d3e9036a82ebf Mon Sep 17 00:00:00 2001 From: edwinQQQ Date: Mon, 28 Jul 2025 18:28:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=E5=A4=9A=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E6=94=AF=E6=8C=81=E4=B8=8E=E6=9C=AC=E5=9C=B0=E5=8C=96?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增多语言问题修复计划文档,详细描述了多语言支持的现状与解决方案。 - 在LocalizationManager中启用全局本地化方法,替换多个视图中的NSLocalizedString调用为LocalizedString。 - 更新MainFeature以确保在MeView标签页时正确加载用户数据。 - 在多个视图中添加语言切换测试区域,确保文本实时更新。 - 修复MeView显示问题,确保用户信息和动态内容正确加载。 --- issues/多语言问题修复.md | 63 +++++++++++++++++ yana/Features/MainFeature.swift | 8 +++ yana/Utils/LocalizationManager.swift | 67 +++++++++++++------ yana/Views/Components/UserAgreementView.swift | 6 +- yana/Views/EMailLoginView.swift | 6 +- yana/Views/FeedListView.swift | 6 +- yana/Views/IDLoginView.swift | 12 ++-- yana/Views/LanguageSettingsView.swift | 26 +++++++ yana/Views/LoginView.swift | 6 +- yana/Views/MeView.swift | 10 +-- 10 files changed, 166 insertions(+), 44 deletions(-) create mode 100644 issues/多语言问题修复.md diff --git a/issues/多语言问题修复.md b/issues/多语言问题修复.md new file mode 100644 index 0000000..19ed1c7 --- /dev/null +++ b/issues/多语言问题修复.md @@ -0,0 +1,63 @@ +# 多语言问题修复计划 + +## 问题描述 +项目配置了多语言支持,默认英文,但应用仍显示中文。原因是大部分视图使用 `NSLocalizedString`,它会读取系统语言设置而不是应用内保存的用户语言选择。 + +## 解决方案 + +### 1. 修复 LocalizationManager +- ✅ 启用了注释的 String 扩展 +- ✅ 添加了全局 `LocalizedString` 方法 +- ✅ 添加了 `LocalizedTextModifier` 结构体 + +### 2. 替换关键界面的本地化方法 +- ✅ LoginView - 应用标题、登录按钮 +- ✅ UserAgreementView - 用户协议文本 +- ✅ FeedListView - 页面标题、空状态、标语 +- ✅ IDLoginView - 标题、占位符、按钮文本 +- ✅ EMailLoginView - 标题、按钮文本 +- ✅ LanguageSettingsView - 添加测试区域 +- ✅ MeView - 用户昵称、ID显示、加载状态、错误信息 + +### 3. 修复 MeView 显示问题 +- ✅ 修复 MainFeature 中的数据加载逻辑 +- ✅ 在 accountModelLoaded 中添加 MeView 数据加载触发 +- ✅ 确保 uid 正确设置时触发数据加载 + +### 4. 新增功能 +- ✅ 全局 `LocalizedString(key, comment:)` 方法 +- ✅ String 扩展:`"key".localized` +- ✅ 语言切换测试区域 + +## 使用方法 + +### 方法1:使用全局方法 +```swift +Text(LocalizedString("login.app_title", comment: "")) +``` + +### 方法2:使用 String 扩展 +```swift +Text("login.app_title".localized) +``` + +## 测试验证 +1. 在语言设置界面可以看到测试区域 +2. 切换语言后,测试区域的文本会实时更新 +3. 所有使用 `LocalizedString` 的界面都会正确显示选择的语言 +4. MeView 现在应该能正确显示用户信息和动态内容 + +## 问题修复详情 + +### MeView 不显示内容的问题 +**原因**:MainFeature 中只有当用户切换到 `.other` 标签页时才会检查 uid 并触发数据加载,但如果 accountModel 为 nil 或 uid 为 0,就不会加载数据。 + +**解决方案**: +1. 在 `accountModelLoaded` 处理中添加 MeView 数据加载逻辑 +2. 确保当 accountModel 加载完成且当前选中 MeView 标签页时,正确设置 uid 并触发数据加载 + +## 后续工作 +- 继续替换其他界面的 `NSLocalizedString` 调用 +- 确保所有用户可见的文本都使用新的本地化方法 +- 测试各种语言切换场景 +- 验证 MeView 在不同登录状态下的显示 \ No newline at end of file diff --git a/yana/Features/MainFeature.swift b/yana/Features/MainFeature.swift index 8dd0616..46b9560 100644 --- a/yana/Features/MainFeature.swift +++ b/yana/Features/MainFeature.swift @@ -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 到设置页,带入当前用户信息 diff --git a/yana/Utils/LocalizationManager.swift b/yana/Utils/LocalizationManager.swift index 696fa36..98afee4 100644 --- a/yana/Utils/LocalizationManager.swift +++ b/yana/Utils/LocalizationManager.swift @@ -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 { + // 这里可以添加动态更新逻辑 + } + } +} diff --git a/yana/Views/Components/UserAgreementView.swift b/yana/Views/Components/UserAgreementView.swift index 7f1c87e..f3a608d 100644 --- a/yana/Views/Components/UserAgreementView.swift +++ b/yana/Views/Components/UserAgreementView.swift @@ -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") diff --git a/yana/Views/EMailLoginView.swift b/yana/Views/EMailLoginView.swift index 102e008..561719e 100644 --- a/yana/Views/EMailLoginView.swift +++ b/yana/Views/EMailLoginView.swift @@ -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) } diff --git a/yana/Views/FeedListView.swift b/yana/Views/FeedListView.swift index c21332a..7481e7c 100644 --- a/yana/Views/FeedListView.swift +++ b/yana/Views/FeedListView.swift @@ -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)) diff --git a/yana/Views/IDLoginView.swift b/yana/Views/IDLoginView.swift index e3b53c4..ad55dbe 100644 --- a/yana/Views/IDLoginView.swift +++ b/yana/Views/IDLoginView.swift @@ -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) } diff --git a/yana/Views/LanguageSettingsView.swift b/yana/Views/LanguageSettingsView.swift index 15393ff..822b360 100644 --- a/yana/Views/LanguageSettingsView.swift +++ b/yana/Views/LanguageSettingsView.swift @@ -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") { diff --git a/yana/Views/LoginView.swift b/yana/Views/LoginView.swift index 5b23863..10a502d 100644 --- a/yana/Views/LoginView.swift +++ b/yana/Views/LoginView.swift @@ -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 // 显示邮箱登录界面 } diff --git a/yana/Views/MeView.swift b/yana/Views/MeView.swift index dd5c585..c40bcf6 100644 --- a/yana/Views/MeView.swift +++ b/yana/Views/MeView.swift @@ -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) -> 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)