增加多国语言

main
v504 2 months ago
parent 032145bf04
commit c8e9fbb9f2

@ -223,6 +223,7 @@
knownRegions = (
en,
Base,
"zh-Hans",
);
mainGroup = 581766262E54241200C1B687;
minimizedProjectReferenceProxies = 1;

@ -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
//
}
}
}
}

@ -1,5 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>zh-Hans</string>
</array>
</dict>
</plist>

@ -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")
}

@ -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

@ -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 语言(阿拉伯语、希伯来语)

@ -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
}
}
// 线

@ -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";

@ -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" = "完成";
Loading…
Cancel
Save