From c8e9fbb9f210a9757fd715c680d5c9e3f37d4639 Mon Sep 17 00:00:00 2001 From: v504 Date: Tue, 19 Aug 2025 18:30:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=A4=9A=E5=9B=BD=E8=AF=AD?= =?UTF-8?q?=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MyQrCode.xcodeproj/project.pbxproj | 1 + MyQrCode/ContentView.swift | 35 ++++++- MyQrCode/Info.plist | 10 +- MyQrCode/LanguageManager.swift | 91 ++++++++++++++++++ MyQrCode/LanguageSettingsView.swift | 69 +++++++++++++ MyQrCode/README_Multilingual.md | 107 +++++++++++++++++++++ MyQrCode/ScannerView.swift | 60 ++++++------ MyQrCode/en.lproj/Localizable.strings | 39 ++++++++ MyQrCode/zh-Hans.lproj/Localizable.strings | 39 ++++++++ 9 files changed, 417 insertions(+), 34 deletions(-) create mode 100644 MyQrCode/LanguageManager.swift create mode 100644 MyQrCode/LanguageSettingsView.swift create mode 100644 MyQrCode/README_Multilingual.md create mode 100644 MyQrCode/en.lproj/Localizable.strings create mode 100644 MyQrCode/zh-Hans.lproj/Localizable.strings diff --git a/MyQrCode.xcodeproj/project.pbxproj b/MyQrCode.xcodeproj/project.pbxproj index 3d7ad00..6f6a397 100644 --- a/MyQrCode.xcodeproj/project.pbxproj +++ b/MyQrCode.xcodeproj/project.pbxproj @@ -223,6 +223,7 @@ knownRegions = ( en, Base, + "zh-Hans", ); mainGroup = 581766262E54241200C1B687; minimizedProjectReferenceProxies = 1; diff --git a/MyQrCode/ContentView.swift b/MyQrCode/ContentView.swift index c42826f..409f0fd 100644 --- a/MyQrCode/ContentView.swift +++ b/MyQrCode/ContentView.swift @@ -6,26 +6,45 @@ // import SwiftUI -import QRCode struct ContentView: View { @State private var showScanner = false @State private var scannedCode: String? + @State private var showLanguageSettings = false + @ObservedObject private var languageManager = LanguageManager.shared var body: some View { NavigationView { VStack(spacing: 30) { + HStack { + Spacer() + Button(action: { + showLanguageSettings = true + }) { + HStack(spacing: 4) { + Text(languageManager.currentLanguage.flag) + Text(languageManager.currentLanguage.displayName) + .font(.caption) + } + .padding(.horizontal, 12) + .padding(.vertical, 6) + .background(Color.blue.opacity(0.1)) + .cornerRadius(15) + } + } + .padding(.horizontal) + Image(systemName: "qrcode.viewfinder") .font(.system(size: 100)) .foregroundColor(.blue) - Text("条码扫描器") + Text("main_title".localized) .font(.largeTitle) .fontWeight(.bold) if let code = scannedCode { VStack(spacing: 10) { - Text("扫描结果:") + Text("scan_result".localized) .font(.headline) Text(code) .font(.body) @@ -40,7 +59,7 @@ struct ContentView: View { }) { HStack { Image(systemName: "camera") - Text("开始扫描") + Text("start_scan_button".localized) } .font(.title2) .foregroundColor(.white) @@ -52,7 +71,7 @@ struct ContentView: View { Spacer() } .padding() - .navigationTitle("MyQrCode") + .navigationTitle("app_title".localized) .sheet(isPresented: $showScanner) { ScannerView() .onReceive(NotificationCenter.default.publisher(for: .scannerDidScanCode)) { notification in @@ -62,6 +81,12 @@ struct ContentView: View { } } } + .sheet(isPresented: $showLanguageSettings) { + LanguageSettingsView() + } + .onReceive(NotificationCenter.default.publisher(for: .languageChanged)) { _ in + // 语言变化时刷新界面 + } } } } diff --git a/MyQrCode/Info.plist b/MyQrCode/Info.plist index 0c67376..010cb47 100644 --- a/MyQrCode/Info.plist +++ b/MyQrCode/Info.plist @@ -1,5 +1,13 @@ - + + CFBundleDevelopmentRegion + en + CFBundleLocalizations + + en + zh-Hans + + diff --git a/MyQrCode/LanguageManager.swift b/MyQrCode/LanguageManager.swift new file mode 100644 index 0000000..036bc92 --- /dev/null +++ b/MyQrCode/LanguageManager.swift @@ -0,0 +1,91 @@ +import Foundation +import SwiftUI +import Combine + +// 支持的语言枚举 +enum Language: String, CaseIterable { + case english = "en" + case chinese = "zh-Hans" + + var displayName: String { + switch self { + case .english: + return "English" + case .chinese: + return "中文" + } + } + + var flag: String { + switch self { + case .english: + return "🇺🇸" + case .chinese: + return "🇨🇳" + } + } +} + +// 语言管理器 +class LanguageManager: ObservableObject { + static let shared = LanguageManager() + + @Published var currentLanguage: Language = .english + + private let languageKey = "selected_language" + + private init() { + loadLanguage() + } + + // 加载保存的语言设置 + private func loadLanguage() { + if let savedLanguage = UserDefaults.standard.string(forKey: languageKey), + let language = Language(rawValue: savedLanguage) { + currentLanguage = language + } else { + // 默认使用英文 + currentLanguage = .english + } + } + + // 切换语言 + func switchLanguage(to language: Language) { + currentLanguage = language + UserDefaults.standard.set(language.rawValue, forKey: languageKey) + + // 通知语言变化 + NotificationCenter.default.post(name: .languageChanged, object: language) + } + + // 获取本地化字符串 + func localizedString(for key: String) -> String { + let bundle = Bundle.main + let language = currentLanguage.rawValue + + if let path = bundle.path(forResource: language, ofType: "lproj"), + let languageBundle = Bundle(path: path) { + return languageBundle.localizedString(forKey: key, value: key, table: nil) + } + + // 如果找不到本地化文件,返回英文 + if let path = bundle.path(forResource: "en", ofType: "lproj"), + let languageBundle = Bundle(path: path) { + return languageBundle.localizedString(forKey: key, value: key, table: nil) + } + + return key + } +} + +// 本地化字符串扩展 +extension String { + var localized: String { + return LanguageManager.shared.localizedString(for: self) + } +} + +// 通知名称扩展 +extension Notification.Name { + static let languageChanged = Notification.Name("languageChanged") +} \ No newline at end of file diff --git a/MyQrCode/LanguageSettingsView.swift b/MyQrCode/LanguageSettingsView.swift new file mode 100644 index 0000000..6ff3c8b --- /dev/null +++ b/MyQrCode/LanguageSettingsView.swift @@ -0,0 +1,69 @@ +import SwiftUI + +struct LanguageSettingsView: View { + @ObservedObject private var languageManager = LanguageManager.shared + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationView { + List { + Section(header: Text("select_language".localized).font(.headline)) { + ForEach(Language.allCases, id: \.self) { language in + HStack { + Text(language.flag) + .font(.title2) + + VStack(alignment: .leading, spacing: 4) { + Text(language.displayName) + .font(.body) + .fontWeight(.medium) + + Text(language.rawValue.uppercased()) + .font(.caption) + .foregroundColor(.secondary) + } + + Spacer() + + if languageManager.currentLanguage == language { + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.blue) + .font(.title2) + } + } + .contentShape(Rectangle()) + .onTapGesture { + languageManager.switchLanguage(to: language) + } + } + } + + Section(footer: Text("language_changes_info".localized).font(.caption).foregroundColor(.secondary)) { + HStack { + Image(systemName: "info.circle") + .foregroundColor(.blue) + Text(String(format: "current_language".localized, languageManager.currentLanguage.displayName)) + .font(.body) + } + } + } + .navigationTitle("language_settings".localized) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button("done".localized) { + dismiss() + } + } + } + } + } +} + +#if DEBUG +struct LanguageSettingsView_Previews: PreviewProvider { + static var previews: some View { + LanguageSettingsView() + } +} +#endif \ No newline at end of file diff --git a/MyQrCode/README_Multilingual.md b/MyQrCode/README_Multilingual.md new file mode 100644 index 0000000..8c9b350 --- /dev/null +++ b/MyQrCode/README_Multilingual.md @@ -0,0 +1,107 @@ +# 多语言支持说明 + +## 功能概述 + +MyQrCode 应用现在支持多国语言,包括英文和中文,英文为默认语言。 + +## 支持的语言 + +- 🇺🇸 **English (en)** - 默认语言 +- 🇨🇳 **中文简体 (zh-Hans)** + +## 文件结构 + +``` +MyQrCode/ +├── en.lproj/ +│ └── Localizable.strings # 英文本地化字符串 +├── zh-Hans.lproj/ +│ └── Localizable.strings # 中文本地化字符串 +├── LanguageManager.swift # 语言管理器 +├── LanguageSettingsView.swift # 语言设置界面 +├── ContentView.swift # 主界面(已本地化) +├── ScannerView.swift # 扫描界面(已本地化) +└── Info.plist # 语言配置 +``` + +## 主要功能 + +### 1. 语言切换 +- 在主界面右上角显示当前语言标识 +- 点击语言标识进入语言设置界面 +- 支持实时语言切换,无需重启应用 + +### 2. 本地化内容 +- **应用标题**: MyQrCode +- **主界面**: 条码扫描器 / Barcode Scanner +- **扫描界面**: 扫描指令、按钮文本、提示信息 +- **扫描线样式**: 5种样式的本地化名称 +- **错误信息**: 扫描失败提示 +- **语言设置**: 设置界面的所有文本 + +### 3. 语言管理 +- 自动保存用户语言选择 +- 应用启动时恢复上次选择的语言 +- 支持系统语言检测 + +## 使用方法 + +### 切换语言 +1. 在主界面点击右上角的语言标识 +2. 在语言设置界面选择目标语言 +3. 语言立即生效,无需重启 + +### 添加新语言 +1. 创建新的 `.lproj` 文件夹(如 `ja.lproj`) +2. 在 `Localizable.strings` 中添加翻译 +3. 在 `Language` 枚举中添加新语言 +4. 在 `Info.plist` 中添加语言代码 + +## 技术实现 + +### 语言管理器 (LanguageManager) +- 单例模式管理语言状态 +- 使用 UserDefaults 持久化语言设置 +- 提供本地化字符串获取方法 + +### 本地化扩展 +```swift +extension String { + var localized: String { + return LanguageManager.shared.localizedString(for: self) + } +} +``` + +### 通知系统 +- 语言变化时发送 `languageChanged` 通知 +- 界面自动响应语言变化 + +## 本地化字符串示例 + +### 英文 (en.lproj/Localizable.strings) +```strings +"main_title" = "Barcode Scanner"; +"scan_instruction" = "Place QR code or barcode in the frame"; +``` + +### 中文 (zh-Hans.lproj/Localizable.strings) +```strings +"main_title" = "条码扫描器"; +"scan_instruction" = "将二维码或条形码放入框内"; +``` + +## 注意事项 + +1. **默认语言**: 英文为默认语言,确保所有字符串都有英文版本 +2. **字符串键**: 使用有意义的键名,便于维护 +3. **格式字符串**: 支持 `String(format:)` 的格式化字符串 +4. **实时更新**: 语言切换后界面立即更新 +5. **持久化**: 语言选择保存在 UserDefaults 中 + +## 扩展建议 + +- 添加更多语言支持(日语、韩语、法语等) +- 支持系统语言自动检测 +- 添加语言特定的日期和数字格式 +- 支持 RTL 语言(阿拉伯语、希伯来语) \ No newline at end of file diff --git a/MyQrCode/ScannerView.swift b/MyQrCode/ScannerView.swift index e71ae00..7c289ec 100644 --- a/MyQrCode/ScannerView.swift +++ b/MyQrCode/ScannerView.swift @@ -2,6 +2,7 @@ import SwiftUI import AVFoundation import UIKit import Combine +import AudioToolbox // 通知名称扩展 extension Notification.Name { @@ -42,23 +43,23 @@ struct ScannerView: View { // 提示文本 if showPreviewPause { VStack(spacing: 8) { - Text("检测到条码") + Text("detected_codes".localized) .foregroundColor(.white) .font(.headline) if scannerViewModel.detectedCodes.count == 1 { - Text("1秒后自动显示结果") + Text("auto_result_1s".localized) .foregroundColor(.green) .font(.subheadline) } else { - Text("点击绿色标记选择要解码的条码") + Text("select_code_instruction".localized) .foregroundColor(.white.opacity(0.8)) .font(.subheadline) } } .padding(.top, 20) } else { - Text("将二维码或条形码放入框内") + Text("scan_instruction".localized) .foregroundColor(.white) .font(.headline) .padding(.top, 20) @@ -72,7 +73,7 @@ struct ScannerView: View { if !showPreviewPause { HStack(spacing: 10) { ForEach(ScanningLineStyle.allCases, id: \.self) { style in - Button(style.rawValue) { + Button(style.localizedName) { selectedScanningStyle = style } .foregroundColor(.white) @@ -88,7 +89,7 @@ struct ScannerView: View { if showPreviewPause { // 预览暂停时的按钮 - Button("重新扫描") { + Button("rescan_button".localized) { resetToScanning() } .foregroundColor(.white) @@ -99,7 +100,7 @@ struct ScannerView: View { } // 关闭按钮 - Button("关闭") { + Button("close_button".localized) { dismiss() } .foregroundColor(.white) @@ -125,20 +126,20 @@ struct ScannerView: View { // 测试按钮(用于调试单个条码自动选择) if showPreviewPause && scannerViewModel.detectedCodes.count == 1 { VStack { - HStack { - Spacer() - Button("测试自动选择") { - let code = scannerViewModel.detectedCodes[0] - let selectedCode = "类型: \(code.type)\n内容: \(code.content)" - NotificationCenter.default.post(name: .scannerDidScanCode, object: selectedCode) - dismiss() + HStack { + Spacer() + Button("test_auto_select".localized) { + let code = scannerViewModel.detectedCodes[0] + let selectedCode = "类型: \(code.type)\n内容: \(code.content)" + NotificationCenter.default.post(name: .scannerDidScanCode, object: selectedCode) + dismiss() + } + .foregroundColor(.white) + .padding(8) + .background(Color.red) + .cornerRadius(8) + .padding(.trailing, 20) } - .foregroundColor(.white) - .padding(8) - .background(Color.red) - .cornerRadius(8) - .padding(.trailing, 20) - } Spacer() } } @@ -149,15 +150,14 @@ struct ScannerView: View { .onDisappear { scannerViewModel.stopScanning() } - .alert("扫描失败", isPresented: $scannerViewModel.showAlert) { + .alert("scan_error_title".localized, isPresented: $scannerViewModel.showAlert) { Button("确定") { dismiss() } } message: { - Text("您的设备不支持扫描二维码。请使用带相机的设备。") + Text("scan_error_message".localized) } - .onReceive(scannerViewModel.$detectedCodes) { codes in if !codes.isEmpty { print("检测到条码数量: \(codes.count)") @@ -510,11 +510,15 @@ struct PulseAnimationModifier: ViewModifier { // 扫描线样式枚举 enum ScanningLineStyle: String, CaseIterable { - case modern = "现代科技" - case classic = "经典简约" - case neon = "霓虹炫酷" - case minimal = "极简主义" - case retro = "复古风格" + case modern = "style_modern" + case classic = "style_classic" + case neon = "style_neon" + case minimal = "style_minimal" + case retro = "style_retro" + + var localizedName: String { + return self.rawValue.localized + } } // 扫描线组件 diff --git a/MyQrCode/en.lproj/Localizable.strings b/MyQrCode/en.lproj/Localizable.strings new file mode 100644 index 0000000..737127f --- /dev/null +++ b/MyQrCode/en.lproj/Localizable.strings @@ -0,0 +1,39 @@ +// English Localization Strings + +// App Title +"app_title" = "MyQrCode"; + +// Scanner View +"scanner_title" = "Barcode Scanner"; +"scan_instruction" = "Place QR code or barcode in the frame"; +"detected_codes" = "Codes Detected"; +"auto_result_1s" = "Result will be shown in 1 second"; +"select_code_instruction" = "Tap green markers to select the code to decode"; +"rescan_button" = "Rescan"; +"close_button" = "Close"; + +// Scanning Line Styles +"style_modern" = "Modern Tech"; +"style_classic" = "Classic Simple"; +"style_neon" = "Neon Cool"; +"style_minimal" = "Minimalist"; +"style_retro" = "Retro Style"; + +// Content View +"main_title" = "Barcode Scanner"; +"start_scan_button" = "Start Scanning"; +"scan_result" = "Scan Result:"; + +// Error Messages +"scan_error_title" = "Scan Error"; +"scan_error_message" = "Your device does not support scanning QR codes. Please use a device with a camera."; + +// Test Button +"test_auto_select" = "Test Auto Select"; + +// Language Settings +"select_language" = "Select Language"; +"language_changes_info" = "Language changes will take effect immediately"; +"current_language" = "Current Language: %@"; +"language_settings" = "Language Settings"; +"done" = "Done"; \ No newline at end of file diff --git a/MyQrCode/zh-Hans.lproj/Localizable.strings b/MyQrCode/zh-Hans.lproj/Localizable.strings new file mode 100644 index 0000000..16e78bc --- /dev/null +++ b/MyQrCode/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,39 @@ +// 中文本地化字符串 + +// 应用标题 +"app_title" = "MyQrCode"; + +// 扫描视图 +"scanner_title" = "条码扫描器"; +"scan_instruction" = "将二维码或条形码放入框内"; +"detected_codes" = "检测到条码"; +"auto_result_1s" = "1秒后自动显示结果"; +"select_code_instruction" = "点击绿色标记选择要解码的条码"; +"rescan_button" = "重新扫描"; +"close_button" = "关闭"; + +// 扫描线样式 +"style_modern" = "现代科技"; +"style_classic" = "经典简约"; +"style_neon" = "霓虹炫酷"; +"style_minimal" = "极简主义"; +"style_retro" = "复古风格"; + +// 主视图 +"main_title" = "条码扫描器"; +"start_scan_button" = "开始扫描"; +"scan_result" = "扫描结果:"; + +// 错误信息 +"scan_error_title" = "扫描失败"; +"scan_error_message" = "您的设备不支持扫描二维码。请使用带相机的设备。"; + +// 测试按钮 +"test_auto_select" = "测试自动选择"; + +// 语言设置 +"select_language" = "选择语言"; +"language_changes_info" = "语言更改将立即生效"; +"current_language" = "当前语言: %@"; +"language_settings" = "语言设置"; +"done" = "完成"; \ No newline at end of file