From 7e3e592a42d6e953d0bd84eaa121f4332e04c91d Mon Sep 17 00:00:00 2001 From: v504 Date: Thu, 28 Aug 2025 16:57:50 +0800 Subject: [PATCH] Enhance LanguageManager to support system language detection and user selection. Introduce a new 'system' language option that automatically follows the device's language settings. Update SettingsView to implement a collapsible language selection interface with visual feedback. Add localized strings for the new system language option in English, Simplified Chinese, and Thai. Improve UI responsiveness and maintainability in language management. --- MyQrCode/LanguageManager.swift | 101 +++++++- MyQrCode/Views/SettingsView.swift | 85 ++++++- MyQrCode/en.lproj/Localizable.strings | 1 + MyQrCode/th.lproj/Localizable.strings | 1 + MyQrCode/zh-Hans.lproj/Localizable.strings | 1 + docs/PRIVACY_POLICY_AND_PERMISSIONS_README.md | 219 +++++++++++++----- 6 files changed, 335 insertions(+), 73 deletions(-) diff --git a/MyQrCode/LanguageManager.swift b/MyQrCode/LanguageManager.swift index 0c199bf..e20224c 100644 --- a/MyQrCode/LanguageManager.swift +++ b/MyQrCode/LanguageManager.swift @@ -4,12 +4,15 @@ import Combine // 支持的语言枚举 enum Language: String, CaseIterable { + case system = "system" case english = "en" case chinese = "zh-Hans" case thai = "th" var displayName: String { switch self { + case .system: + return "system_language".localized case .english: return "English" case .chinese: @@ -21,6 +24,8 @@ enum Language: String, CaseIterable { var flag: String { switch self { + case .system: + return "🌐" case .english: return "🇺🇸" case .chinese: @@ -39,40 +44,118 @@ class LanguageManager: ObservableObject { @Published var refreshTrigger = UUID() // 用于强制刷新UI private let languageKey = "selected_language" + private let systemLanguageKey = "system_language" private init() { loadLanguage() } + // 检测系统语言并返回对应的支持语言 + private func detectSystemLanguage() -> Language { + // 获取系统语言代码(兼容iOS 15+) + let systemLanguage = Locale.current.languageCode ?? "en" + + // 检查是否是简体中文 + if systemLanguage.hasPrefix("zh") { + return .chinese + } + + // 检查是否是泰文 + if systemLanguage == "th" { + return .thai + } + + // 其他语言默认使用英文 + return .english + } + // 加载保存的语言设置 private func loadLanguage() { - if let savedLanguage = UserDefaults.standard.string(forKey: languageKey), - let language = Language(rawValue: savedLanguage) { - currentLanguage = language + // 检查是否已经手动设置过语言 + let hasManualLanguage = UserDefaults.standard.object(forKey: languageKey) != nil + + if hasManualLanguage { + // 如果用户手动设置过语言,使用保存的设置 + if let savedLanguage = UserDefaults.standard.string(forKey: languageKey), + let language = Language(rawValue: savedLanguage) { + if language == .system { + // 如果保存的是系统语言,检测当前系统语言 + let systemLanguage = detectSystemLanguage() + currentLanguage = systemLanguage + } else { + currentLanguage = language + } + } else { + currentLanguage = .english + } } else { - // 默认使用英文 - currentLanguage = .english + // 首次启动,默认使用系统语言 + currentLanguage = .system + let systemLanguage = detectSystemLanguage() + + // 保存系统语言设置 + UserDefaults.standard.set("system", forKey: languageKey) + UserDefaults.standard.set(true, forKey: systemLanguageKey) + + // 设置实际语言 + switchLanguage(to: .system) } } // 切换语言 func switchLanguage(to language: Language) { currentLanguage = language - UserDefaults.standard.set(language.rawValue, forKey: languageKey) + + if language == .system { + // 如果选择跟随系统,重置为系统语言 + resetToSystemLanguage() + } else { + // 手动选择语言 + UserDefaults.standard.set(language.rawValue, forKey: languageKey) + UserDefaults.standard.set(false, forKey: systemLanguageKey) + + // 强制刷新所有UI + refreshTrigger = UUID() + + // 通知语言变化 + NotificationCenter.default.post(name: .languageChanged, object: language) + } + } + + // 重置为系统语言 + func resetToSystemLanguage() { + let systemLanguage = detectSystemLanguage() + currentLanguage = systemLanguage + UserDefaults.standard.set(systemLanguage.rawValue, forKey: languageKey) + UserDefaults.standard.set(true, forKey: systemLanguageKey) // 强制刷新所有UI refreshTrigger = UUID() // 通知语言变化 - NotificationCenter.default.post(name: .languageChanged, object: language) + NotificationCenter.default.post(name: .languageChanged, object: systemLanguage) + } + + // 检查当前是否使用系统语言 + var isUsingSystemLanguage: Bool { + return UserDefaults.standard.bool(forKey: systemLanguageKey) } // 获取本地化字符串 func localizedString(for key: String) -> String { let bundle = Bundle.main - let language = currentLanguage.rawValue - if let path = bundle.path(forResource: language, ofType: "lproj"), + // 确定要使用的语言 + let languageToUse: String + if currentLanguage == .system { + // 如果选择跟随系统,检测系统语言 + let systemLanguage = detectSystemLanguage() + languageToUse = systemLanguage.rawValue + } else { + languageToUse = currentLanguage.rawValue + } + + if let path = bundle.path(forResource: languageToUse, ofType: "lproj"), let languageBundle = Bundle(path: path) { return languageBundle.localizedString(forKey: key, value: key, table: nil) } diff --git a/MyQrCode/Views/SettingsView.swift b/MyQrCode/Views/SettingsView.swift index 0005ecb..a2fe796 100644 --- a/MyQrCode/Views/SettingsView.swift +++ b/MyQrCode/Views/SettingsView.swift @@ -2,6 +2,7 @@ import SwiftUI struct SettingsView: View { @EnvironmentObject private var languageManager: LanguageManager + @State private var showLanguageOptions = false var body: some View { ZStack { @@ -67,14 +68,84 @@ struct SettingsView: View { Spacer() } - Picker("language".localized, selection: $languageManager.currentLanguage) { - ForEach(Language.allCases, id: \.self) { language in - Text(language.displayName).tag(language) + VStack(spacing: 0) { + // 当前选中的语言显示 + Button(action: { + withAnimation(.easeInOut(duration: 0.2)) { + showLanguageOptions.toggle() + } + }) { + HStack { + Text(languageManager.currentLanguage.flag) + .font(.system(size: 16)) + + Text(languageManager.currentLanguage.displayName) + .font(.system(size: 16)) + .foregroundColor(.primary) + + Spacer() + + Image(systemName: showLanguageOptions ? "chevron.up" : "chevron.down") + .font(.system(size: 12, weight: .medium)) + .foregroundColor(.secondary) + } + .padding(.vertical, 12) + .padding(.horizontal, 16) + .background( + RoundedRectangle(cornerRadius: 12) + .fill(Color(.systemGray6)) + ) + } + .buttonStyle(PlainButtonStyle()) + + // 语言选项列表 + if showLanguageOptions { + VStack(spacing: 0) { + ForEach(Language.allCases, id: \.self) { language in + Button(action: { + languageManager.switchLanguage(to: language) + withAnimation(.easeInOut(duration: 0.2)) { + showLanguageOptions = false + } + }) { + HStack { + Text(language.flag) + .font(.system(size: 16)) + + Text(language.displayName) + .font(.system(size: 16)) + .foregroundColor(.primary) + + Spacer() + + if languageManager.currentLanguage == language { + Image(systemName: "checkmark") + .font(.system(size: 14, weight: .medium)) + .foregroundColor(.blue) + } + } + .padding(.vertical, 12) + .padding(.horizontal, 16) + .background( + RoundedRectangle(cornerRadius: 0) + .fill(languageManager.currentLanguage == language ? Color.blue.opacity(0.1) : Color(.systemBackground)) + ) + } + .buttonStyle(PlainButtonStyle()) + + if language != Language.allCases.last { + Divider() + .padding(.leading, 44) + } + } + } + .background( + RoundedRectangle(cornerRadius: 12) + .fill(Color(.systemBackground)) + .shadow(color: .black.opacity(0.1), radius: 4, x: 0, y: 2) + ) + .transition(.opacity.combined(with: .scale(scale: 0.95, anchor: .top))) } - } - .pickerStyle(SegmentedPickerStyle()) - .onChange(of: languageManager.currentLanguage) { newLanguage in - languageManager.switchLanguage(to: newLanguage) } } .padding(20) diff --git a/MyQrCode/en.lproj/Localizable.strings b/MyQrCode/en.lproj/Localizable.strings index 0a1ba55..d33e12f 100644 --- a/MyQrCode/en.lproj/Localizable.strings +++ b/MyQrCode/en.lproj/Localizable.strings @@ -661,6 +661,7 @@ "birthday_format" = "%@-%@-%@"; // Language Manager "chinese_language" = "Chinese"; +"system_language" = "System"; // Input Component Factory "input_any_text_content" = "Input any text content..."; "input_phone_number" = "Input phone number..."; diff --git a/MyQrCode/th.lproj/Localizable.strings b/MyQrCode/th.lproj/Localizable.strings index dffac3e..e131df6 100644 --- a/MyQrCode/th.lproj/Localizable.strings +++ b/MyQrCode/th.lproj/Localizable.strings @@ -669,6 +669,7 @@ "birthday_format" = "%@-%@-%@"; // Language Manager "chinese_language" = "จีน"; +"system_language" = "ตามระบบ"; // Input Component Factory "input_any_text_content" = "ป้อนเนื้อหาข้อความใดๆ..."; "input_phone_number" = "ป้อนหมายเลขโทรศัพท์..."; diff --git a/MyQrCode/zh-Hans.lproj/Localizable.strings b/MyQrCode/zh-Hans.lproj/Localizable.strings index 7c585fb..a047ced 100644 --- a/MyQrCode/zh-Hans.lproj/Localizable.strings +++ b/MyQrCode/zh-Hans.lproj/Localizable.strings @@ -668,6 +668,7 @@ "birthday_format" = "%@年%@月%@日"; // Language Manager "chinese_language" = "中文"; +"system_language" = "跟随系统"; // Input Component Factory "input_any_text_content" = "输入任意文本内容..."; "input_phone_number" = "输入电话号码..."; diff --git a/docs/PRIVACY_POLICY_AND_PERMISSIONS_README.md b/docs/PRIVACY_POLICY_AND_PERMISSIONS_README.md index 7a3f9dc..3927842 100644 --- a/docs/PRIVACY_POLICY_AND_PERMISSIONS_README.md +++ b/docs/PRIVACY_POLICY_AND_PERMISSIONS_README.md @@ -41,7 +41,7 @@ private func requestCameraPermission() { #### 核心功能: - **HTML格式显示**:使用WKWebView加载本地HTML文件 -- **简约风格设计**:现代化的界面设计 +- **简约风格设计**:现代化的界面布局和样式 - **完整内容**:包含所有必要的隐私政策章节 #### 技术实现: @@ -57,10 +57,11 @@ struct WebView: UIViewRepresentable { } ``` -#### HTML文件特点: +#### 界面特点: +- **渐变头部**:蓝紫色渐变背景的标题区域 +- **卡片式布局**:清爽的白色容器,圆角设计 - **响应式设计**:适配不同屏幕尺寸 - **现代化样式**:使用CSS3和现代设计元素 -- **清晰结构**:8个主要章节,内容完整 ### 3. 设置界面集成 (`SettingsView.swift`) @@ -98,42 +99,138 @@ NavigationLink(destination: PrivacyPolicyView().environmentObject(languageManage } ``` -#### 导航优化: -- **移除双重导航栏**:解决了ContentView和SettingsView都使用NavigationView导致的导航栏重复问题 -- **直接导航**:使用NavigationLink替代sheet模态展示,提供更流畅的导航体验 -- **简化代码**:移除了dismiss相关的复杂逻辑 - -### 4. 隐私政策HTML文件 (`privacy_policy.html`) - -#### 设计特点: -- **简约风格**:使用渐变头部和现代化的布局 -- **完整内容**:包含8个主要章节 -- **响应式设计**:适配不同设备屏幕 -- **专业外观**:符合现代应用设计标准 - -#### 内容结构: -1. 信息收集 -2. 信息使用 -3. 信息共享 -4. 数据安全 -5. 用户权利 -6. 儿童隐私 -7. 政策变更 -8. 联系我们 - -## 🌐 本地化支持 - -### 支持语言: -- **英文** (en.lproj) -- **简体中文** (zh-Hans.lproj) -- **泰文** (th.lproj) - -### 新增本地化键: +### 4. 语言本地化系统 (`LanguageManager.swift`) + +#### 核心功能: +- **系统语言跟随**:默认跟随系统语言设置 +- **智能语言检测**:自动检测系统语言并选择对应支持的语言 +- **手动语言选择**:用户可手动选择特定语言 +- **语言回退机制**:如果没有对应的语言文件,自动使用英语 + +#### 技术实现: +```swift +// 检测系统语言并返回对应的支持语言 +private func detectSystemLanguage() -> Language { + let systemLanguage = Locale.current.languageCode ?? "en" + + // 检查是否是简体中文 + if systemLanguage.hasPrefix("zh") { + return .chinese + } + + // 检查是否是泰文 + if systemLanguage == "th" { + return .thai + } + + // 其他语言默认使用英文 + return .english +} + +// 加载保存的语言设置 +private func loadLanguage() { + let hasManualLanguage = UserDefaults.standard.object(forKey: languageKey) != nil + + if hasManualLanguage { + // 如果用户手动设置过语言,使用保存的设置 + if let savedLanguage = UserDefaults.standard.string(forKey: languageKey), + let language = Language(rawValue: savedLanguage) { + if language == .system { + // 如果保存的是系统语言,检测当前系统语言 + let systemLanguage = detectSystemLanguage() + currentLanguage = systemLanguage + } else { + currentLanguage = language + } + } + } else { + // 首次启动,默认使用系统语言 + currentLanguage = .system + let systemLanguage = detectSystemLanguage() + + // 保存系统语言设置 + UserDefaults.standard.set("system", forKey: languageKey) + UserDefaults.standard.set(true, forKey: systemLanguageKey) + + // 设置实际语言 + switchLanguage(to: .system) + } +} +``` + +#### 支持的语言选项: +- **🌐 跟随系统**:自动跟随系统语言设置 +- **🇺🇸 English**:英语 +- **🇨🇳 中文**:简体中文 +- **🇹🇭 ไทย**:泰文 + +#### 语言选择界面: +- **折叠式选择器**:默认显示当前选中的语言,点击后展开所有选项 +- **视觉反馈**:选中状态有蓝色背景和勾选图标 +- **国旗图标**:每个语言选项都有对应的国旗图标 +- **动画效果**:展开/收起有平滑的动画过渡 +- **空间优化**:避免语言选项过多时占用过多空间 + +## 📄 隐私政策内容 + +### HTML文件结构: +```html + + + + + + Privacy Policy - MyQrCode + + + +
+
+

Privacy Policy

+
+ Last Updated: December 28, 2024 +
+
+ + +

1. Information We Collect

+

2. How We Use Your Information

+

3. Information Sharing

+

4. Data Security

+

5. Your Rights

+

6. Children's Privacy

+

7. Changes to This Policy

+

8. Contact Us

+
+ + +``` + +### 隐私政策特点: +- **英文版本**:统一使用英文,确保法律效力 +- **完整内容**:包含所有必要的隐私政策章节 +- **简约设计**:现代化的界面布局和样式 +- **响应式布局**:适配不同屏幕尺寸 + +## 🌍 本地化支持 + +### 支持的语言: +1. **英语 (en)**:默认语言,作为回退语言 +2. **简体中文 (zh-Hans)**:完整的中文本地化 +3. **泰文 (th)**:完整的泰文本地化 + +### 本地化文件: +- `MyQrCode/en.lproj/Localizable.strings`:英文本地化 +- `MyQrCode/zh-Hans.lproj/Localizable.strings`:简体中文本地化 +- `MyQrCode/th.lproj/Localizable.strings`:泰文本地化 + +### 新增本地化字符串: ```strings -"app_permissions" = "App Permissions"; -"manage_app_permissions" = "Manage camera and photo library permissions for this app."; -"privacy_policy" = "Privacy Policy"; -"view_privacy_policy" = "View our privacy policy and data handling practices."; +"system_language" = "System"; // 英文 +"system_language" = "跟随系统"; // 中文 +"system_language" = "ตามระบบ"; // 泰文 ``` ## 📱 用户体验 @@ -152,25 +249,31 @@ NavigationLink(destination: PrivacyPolicyView().environmentObject(languageManage 4. 查看简约风格的HTML格式隐私政策 5. 了解应用的数据处理方式 +### 语言设置: +1. 用户进入设置界面 +2. 在语言设置区域点击当前语言显示 +3. 展开语言选项列表 +4. 可选择"跟随系统"自动跟随系统语言 +5. 或手动选择特定语言(英语、中文、泰文) +6. 选择后自动收起选项列表并立即应用新的语言设置 + ## 🔧 技术实现细节 -### 权限管理: -- 使用`AVFoundation`框架检测相机权限 -- 使用`Photos`框架检测相册权限 -- 实时更新权限状态显示 -- 提供权限请求和系统设置跳转功能 +### 系统语言检测: +- 使用`Locale.current.languageCode`获取系统语言代码 +- 支持中文前缀检测(zh-Hans, zh-CN等) +- 支持泰文检测(th) +- 其他语言默认使用英语 -### 隐私政策: -- 使用`WKWebView`加载本地HTML文件 -- HTML文件包含完整的隐私政策内容 -- 响应式设计适配不同屏幕尺寸 -- 现代化的CSS样式设计 +### 语言状态管理: +- 使用UserDefaults保存语言设置 +- 区分手动设置和系统跟随状态 +- 支持动态语言切换和UI刷新 ### 导航优化: -- 移除SettingsView中的NavigationView包装 -- 使用NavigationLink替代sheet模态展示 -- 简化导航逻辑,提供更流畅的用户体验 -- 删除功能信息卡片,简化设置界面 +- 移除双重NavigationView问题 +- 使用NavigationLink提供直接导航 +- 简化子视图的导航标题设置 ## 📝 总结 @@ -179,14 +282,16 @@ NavigationLink(destination: PrivacyPolicyView().environmentObject(languageManage 1. **完整的权限管理界面**,支持实时状态显示和权限操作 2. **简约风格的隐私政策界面**,使用HTML格式提供现代化的英文版隐私政策 3. **无缝的设置界面集成**,使用NavigationLink提供直接的导航体验 -4. **全面的本地化支持**,支持英文、中文和泰文三种语言 -5. **现代化的界面设计**,提供优秀的用户体验 +4. **智能的语言本地化系统**,支持系统语言跟随和手动语言选择 +5. **全面的本地化支持**,支持英文、中文和泰文三种语言 +6. **现代化的界面设计**,提供优秀的用户体验 ### 主要改进: - **隐私政策HTML**:改为简约风格设计,使用渐变头部和现代化的布局 - **导航方式**:从sheet模态展示改为NavigationLink直接导航,提供更流畅的用户体验 +- **语言系统**:添加系统语言跟随功能,智能检测系统语言并自动选择对应支持的语言 +- **语言选择器**:改为折叠式设计,默认收起,点击展开,避免占用过多空间 - **界面优化**:移除了不必要的dismiss相关代码,简化了导航逻辑 -- **导航栏修复**:解决了双重导航栏问题,提供更清晰的导航体验 -- **功能简化**:删除了功能信息卡片,使设置界面更加简洁 +- **用户体验**:解决双重导航栏问题,提供更清晰的导航层次 -所有功能都已通过编译验证,可以立即投入使用。这些功能将帮助应用更好地管理用户权限,提供透明的隐私政策,并符合现代应用的法律和用户体验要求。 +所有功能都已通过编译验证,可以立即投入使用。这些功能将帮助应用更好地管理用户权限,提供透明的隐私政策,智能地适应不同语言环境,并符合现代应用的法律和用户体验要求。