From b5b73e83e9e999e7dbcd26a2449e0eb8f9606088 Mon Sep 17 00:00:00 2001 From: v504 Date: Fri, 29 Aug 2025 17:30:47 +0800 Subject: [PATCH] Remove add_missing_localizations.py script and enhance Info.plist with URL schemes for social apps and calendar access descriptions. Update localization strings for contact and calendar functionalities across multiple languages, ensuring comprehensive support and improved user experience. --- MyQrCode.xcodeproj/project.pbxproj | 4 + .../xcdebugger/Breakpoints_v2.xcbkptlist | 144 ++++ MyQrCode/Info.plist | 27 + MyQrCode/Models/HistoryEnums.swift | 41 + MyQrCode/Models/QRCodeParser.swift | 503 ++++++++---- MyQrCode/Utils/CalendarManager.swift | 160 ++++ MyQrCode/Utils/ContactManager.swift | 168 ++++ MyQrCode/Views/History/QRCodeDetailView.swift | 716 ++++++++++++++-- MyQrCode/Views/Settings/SettingsView.swift | 5 - MyQrCode/de.lproj/InfoPlist.strings | 1 + MyQrCode/de.lproj/Localizable.strings | 66 ++ MyQrCode/en.lproj/InfoPlist.strings | 3 + MyQrCode/en.lproj/Localizable.strings | 65 ++ MyQrCode/es.lproj/InfoPlist.strings | 1 + MyQrCode/es.lproj/Localizable.strings | 66 ++ MyQrCode/fr.lproj/InfoPlist.strings | 1 + MyQrCode/fr.lproj/Localizable.strings | 66 ++ MyQrCode/it.lproj/InfoPlist.strings | 1 + MyQrCode/it.lproj/Localizable.strings | 66 ++ MyQrCode/ja.lproj/InfoPlist.strings | 1 + MyQrCode/ja.lproj/Localizable.strings | 66 ++ MyQrCode/ko.lproj/InfoPlist.strings | 1 + MyQrCode/ko.lproj/Localizable.strings | 66 ++ MyQrCode/pt.lproj/InfoPlist.strings | 1 + MyQrCode/pt.lproj/Localizable.strings | 66 ++ MyQrCode/pt.lproj/Localizable.strings.backup | 762 ++++++++++++++++++ MyQrCode/ru.lproj/InfoPlist.strings | 1 + MyQrCode/ru.lproj/Localizable.strings | 66 ++ MyQrCode/ru.lproj/Localizable.strings.backup | 762 ++++++++++++++++++ MyQrCode/th.lproj/InfoPlist.strings | 1 + MyQrCode/th.lproj/Localizable.strings | 54 ++ MyQrCode/zh-Hans.lproj/InfoPlist.strings | 3 + MyQrCode/zh-Hans.lproj/Localizable.strings | 65 ++ add_missing_localizations.py | 97 --- docs/CONTACT_FEATURE_ENHANCEMENT_README.md | 316 ++++++++ 35 files changed, 4091 insertions(+), 341 deletions(-) create mode 100644 MyQrCode/Utils/CalendarManager.swift create mode 100644 MyQrCode/Utils/ContactManager.swift create mode 100644 MyQrCode/pt.lproj/Localizable.strings.backup create mode 100644 MyQrCode/ru.lproj/Localizable.strings.backup delete mode 100644 add_missing_localizations.py create mode 100644 docs/CONTACT_FEATURE_ENHANCEMENT_README.md diff --git a/MyQrCode.xcodeproj/project.pbxproj b/MyQrCode.xcodeproj/project.pbxproj index ace36e0..e4ff1b0 100644 --- a/MyQrCode.xcodeproj/project.pbxproj +++ b/MyQrCode.xcodeproj/project.pbxproj @@ -448,7 +448,9 @@ INFOPLIST_FILE = MyQrCode/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = MyQrCode; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_NSCalendarsUsageDescription = "$(PRODUCT_NAME) needs access to calendar to add events"; INFOPLIST_KEY_NSCameraUsageDescription = "Need to access the camera to scan QR codes and barcodes."; + INFOPLIST_KEY_NSContactsUsageDescription = "$(PRODUCT_NAME) needs access to contacts to add contact information"; INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Need to access the photo gallery to select a custom logo image."; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Need to access the photo gallery to select a custom logo image."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -488,7 +490,9 @@ INFOPLIST_FILE = MyQrCode/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = MyQrCode; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_NSCalendarsUsageDescription = "$(PRODUCT_NAME) needs access to calendar to add events"; INFOPLIST_KEY_NSCameraUsageDescription = "Need to access the camera to scan QR codes and barcodes."; + INFOPLIST_KEY_NSContactsUsageDescription = "$(PRODUCT_NAME) needs access to contacts to add contact information"; INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Need to access the photo gallery to select a custom logo image."; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Need to access the photo gallery to select a custom logo image."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; diff --git a/MyQrCode.xcodeproj/xcuserdata/yc.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/MyQrCode.xcodeproj/xcuserdata/yc.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index bd20cd6..b80ee3d 100644 --- a/MyQrCode.xcodeproj/xcuserdata/yc.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/MyQrCode.xcodeproj/xcuserdata/yc.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -180,5 +180,149 @@ landmarkType = "24"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MyQrCode/Info.plist b/MyQrCode/Info.plist index fb23f36..a120af9 100644 --- a/MyQrCode/Info.plist +++ b/MyQrCode/Info.plist @@ -16,10 +16,37 @@ ru th + CFBundleURLTypes + + + CFBundleURLName + com.abe.example.demo.MyQrCode.phone + CFBundleURLSchemes + + tel + sms + + + + CFBundleURLName + com.abe.example.demo.MyQrCode.social + CFBundleURLSchemes + + instagram + fb + twitter + whatsapp + viber + spotify + + + NSAppTransportSecurity NSExceptionDomains + NSCalendarsWriteOnlyAccessUsageDescription + $(PRODUCT_NAME) needs write-only access to calendar to add events diff --git a/MyQrCode/Models/HistoryEnums.swift b/MyQrCode/Models/HistoryEnums.swift index 7109a46..2622ee9 100644 --- a/MyQrCode/Models/HistoryEnums.swift +++ b/MyQrCode/Models/HistoryEnums.swift @@ -153,6 +153,47 @@ public struct WiFiDetails: Codable { } } +// MARK: - Email详细信息 +public struct EmailDetails: Codable { + public let emailAddress: String + public let subject: String + public let body: String + + public init(emailAddress: String, subject: String = "", body: String = "") { + self.emailAddress = emailAddress + self.subject = subject + self.body = body + } +} + +// MARK: - SMS详细信息 +public struct SMSDetails: Codable { + public let phoneNumber: String + public let message: String + + public init(phoneNumber: String, message: String) { + self.phoneNumber = phoneNumber + self.message = message + } +} + +// MARK: - 日历详细信息 +public struct CalendarDetails: Codable { + public let summary: String + public let startTime: String + public let endTime: String + public let location: String + public let description: String + + public init(summary: String, startTime: String, endTime: String, location: String, description: String) { + self.summary = summary + self.startTime = startTime + self.endTime = endTime + self.location = location + self.description = description + } +} + // MARK: - 二维码解析数据 @objc(ParsedQRData) public class ParsedQRData: NSObject, NSSecureCoding { diff --git a/MyQrCode/Models/QRCodeParser.swift b/MyQrCode/Models/QRCodeParser.swift index a8694d7..0afed2a 100644 --- a/MyQrCode/Models/QRCodeParser.swift +++ b/MyQrCode/Models/QRCodeParser.swift @@ -133,77 +133,77 @@ class QRCodeParser { let trimmedContent = content.trimmingCharacters(in: .whitespacesAndNewlines) // Wi-Fi - if trimmedContent.hasPrefix("WIFI:") { + if trimmedContent.uppercased().hasPrefix("WIFI:") { return parseWiFi(trimmedContent) } // Email - if trimmedContent.hasPrefix("mailto:") { + if trimmedContent.lowercased().hasPrefix("mailto:") { return parseEmail(trimmedContent) } // Phone - if trimmedContent.hasPrefix("tel:") { + if trimmedContent.lowercased().hasPrefix("tel:") { return parsePhone(trimmedContent) } // SMS - if trimmedContent.hasPrefix("SMSTO:") { + if trimmedContent.uppercased().hasPrefix("SMSTO:") { return parseSMS(trimmedContent) } // vCard - if trimmedContent.hasPrefix("BEGIN:VCARD") { + if trimmedContent.uppercased().hasPrefix("BEGIN:VCARD") { return parseVCard(trimmedContent) } // MeCard - if trimmedContent.hasPrefix("MECARD:") { + if trimmedContent.uppercased().hasPrefix("MECARD:") { return parseMeCard(trimmedContent) } // Calendar - if trimmedContent.hasPrefix("BEGIN:VEVENT") { + if trimmedContent.uppercased().hasPrefix("BEGIN:VEVENT") { return parseCalendar(trimmedContent) } // Instagram - if trimmedContent.hasPrefix("instagram://user?username=") { + if trimmedContent.lowercased().hasPrefix("instagram://user?username=") { return parseInstagram(trimmedContent) } // Facebook - if trimmedContent.hasPrefix("fb://profile/") { + if trimmedContent.lowercased().hasPrefix("fb://profile/") { return parseFacebook(trimmedContent) } // Spotify - if trimmedContent.hasPrefix("spotify:search:") { + if trimmedContent.lowercased().hasPrefix("spotify:search:") { return parseSpotify(trimmedContent) } // X (Twitter) - if trimmedContent.hasPrefix("twitter://user?screen_name=") || trimmedContent.contains("x.com") || trimmedContent.contains("twitter.com") { + if trimmedContent.lowercased().hasPrefix("twitter://user?screen_name=") || trimmedContent.lowercased().contains("x.com") || trimmedContent.lowercased().contains("twitter.com") { return parseTwitter(trimmedContent) } // WhatsApp - if trimmedContent.hasPrefix("whatsapp://send?phone=") { + if trimmedContent.lowercased().hasPrefix("whatsapp://send?phone=") { return parseWhatsApp(trimmedContent) } // Viber - if trimmedContent.hasPrefix("viber://add?number=") { + if trimmedContent.lowercased().hasPrefix("viber://add?number=") { return parseViber(trimmedContent) } // Snapchat - if trimmedContent.hasPrefix("snapchat://") { + if trimmedContent.lowercased().hasPrefix("snapchat://") { return parseSnapchat(trimmedContent) } // TikTok - if trimmedContent.contains("tiktok.com") || trimmedContent.contains("www.tiktok.com") { + if trimmedContent.lowercased().contains("tiktok.com") || trimmedContent.lowercased().contains("www.tiktok.com") { return parseTikTok(trimmedContent) } @@ -213,7 +213,7 @@ class QRCodeParser { } // Location - if trimmedContent.hasPrefix("geo:") { + if trimmedContent.lowercased().hasPrefix("geo:") { return parseLocation(trimmedContent) } @@ -228,7 +228,7 @@ class QRCodeParser { // MARK: - 解析Wi-Fi private static func parseWiFi(_ content: String) -> ParsedQRData { - let wifiInfo = content.replacingOccurrences(of: "WIFI:", with: "") + let wifiInfo = content.replacingOccurrences(of: "WIFI:", with: "", options: .caseInsensitive) let components = wifiInfo.components(separatedBy: ";") var ssid = "" @@ -236,11 +236,11 @@ class QRCodeParser { var encryption = "WPA" for component in components { - if component.hasPrefix("S:") { + if component.uppercased().hasPrefix("S:") { ssid = String(component.dropFirst(2)) - } else if component.hasPrefix("P:") { + } else if component.uppercased().hasPrefix("P:") { password = String(component.dropFirst(2)) - } else if component.hasPrefix("T:") { + } else if component.uppercased().hasPrefix("T:") { encryption = String(component.dropFirst(2)) } } @@ -263,19 +263,59 @@ class QRCodeParser { // MARK: - 解析Email private static func parseEmail(_ content: String) -> ParsedQRData { - let email = content.replacingOccurrences(of: "mailto:", with: "") + let emailInfo = content.replacingOccurrences(of: "mailto:", with: "", options: .caseInsensitive) + let components = emailInfo.components(separatedBy: "?") + + let emailAddress = components.first ?? "" + var subject = "" + var body = "" + + // 解析查询参数 + if components.count > 1 { + let queryString = components[1] + let queryParams = queryString.components(separatedBy: "&") + + for param in queryParams { + let keyValue = param.components(separatedBy: "=") + if keyValue.count == 2 { + let key = keyValue[0].lowercased() + let value = keyValue[1].removingPercentEncoding ?? keyValue[1] + + if key == "subject" { + subject = value + } else if key == "body" { + body = value + } + } + } + } + + // 格式化显示信息 + var subtitle = emailAddress + if !subject.isEmpty { + subtitle += String(format: "\n主题: %@", subject) + } + if !body.isEmpty { + let truncatedBody = body.count > 100 ? String(body.prefix(100)) + "..." : body + subtitle += String(format: "\n内容: %@", truncatedBody) + } + + // 存储Email详细信息到额外数据中 + let emailDetails = EmailDetails(emailAddress: emailAddress, subject: subject, body: body) + let extraData = try? JSONEncoder().encode(emailDetails) return ParsedQRData( type: .mail, title: "email_address".localized, - subtitle: email, - icon: "envelope" + subtitle: subtitle, + icon: "envelope", + extraData: extraData ) } // MARK: - 解析Phone private static func parsePhone(_ content: String) -> ParsedQRData { - let phone = content.replacingOccurrences(of: "tel:", with: "") + let phone = content.replacingOccurrences(of: "tel:", with: "", options: .caseInsensitive) return ParsedQRData( type: .phone, @@ -287,20 +327,36 @@ class QRCodeParser { // MARK: - 解析SMS private static func parseSMS(_ content: String) -> ParsedQRData { - let smsInfo = content.replacingOccurrences(of: "SMSTO:", with: "") + let smsInfo = content.replacingOccurrences(of: "SMSTO:", with: "", options: .caseInsensitive) let components = smsInfo.components(separatedBy: ":") - let phone = components.first ?? "" - let message = components.count > 1 ? components[1] : "" + let phone = components.first?.trimmingCharacters(in: .whitespaces) ?? "" + let message = components.count > 1 ? components[1].trimmingCharacters(in: .whitespaces) : "" let title = "sms".localized - let subtitle = String(format: "sms_number_content".localized, phone, message) + var subtitle = "" + + // 构建更美观的副标题 + if !phone.isEmpty { + subtitle += String(format: "📱 %@", phone) + } + + if !message.isEmpty { + if !subtitle.isEmpty { subtitle += "\n" } + let truncatedMessage = message.count > 100 ? String(message.prefix(100)) + "..." : message + subtitle += String(format: "💬 %@", truncatedMessage) + } + + // 创建SMS信息结构体 + let smsDetails = SMSDetails(phoneNumber: phone, message: message) + let extraData = try? JSONEncoder().encode(smsDetails) return ParsedQRData( type: .sms, title: title, subtitle: subtitle, - icon: "message" + icon: "message", + extraData: extraData ) } @@ -308,151 +364,59 @@ class QRCodeParser { private static func parseVCard(_ content: String) -> ParsedQRData { // 标准化vCard为3.0版本 let normalizedVCard = VCardConverter.normalizeVCard(content) - let lines = normalizedVCard.components(separatedBy: .newlines) - - var name = "" - var phone = "" - var email = "" - var company = "" - var title = "" - var address = "" - var website = "" - - for line in lines { - let trimmedLine = line.trimmingCharacters(in: .whitespaces) - if trimmedLine.hasPrefix("FN:") { - name = String(trimmedLine.dropFirst(3)) - } else if trimmedLine.hasPrefix("TEL") { - let telValue = String(trimmedLine.dropFirst(3)) - if telValue.contains(":") { - let number = telValue.components(separatedBy: ":").last ?? "" - phone = number - } - } else if trimmedLine.hasPrefix("EMAIL") { - let emailValue = String(trimmedLine.dropFirst(5)) - if emailValue.contains(":") { - let emailAddress = emailValue.components(separatedBy: ":").last ?? "" - email = emailAddress - } - } else if trimmedLine.hasPrefix("ORG:") { - company = String(trimmedLine.dropFirst(4)) - } else if trimmedLine.hasPrefix("TITLE:") { - title = String(trimmedLine.dropFirst(6)) - } else if trimmedLine.hasPrefix("ADR") { - let adrValue = String(trimmedLine.dropFirst(3)) - if adrValue.contains(":") { - let addressParts = adrValue.components(separatedBy: ":") - if addressParts.count > 1 { - let addressComponents = addressParts[1].components(separatedBy: ";") - if addressComponents.count >= 3 { - address = "\(addressComponents[2]) \(addressComponents[1])" - } - } - } - } else if trimmedLine.hasPrefix("URL:") { - website = String(trimmedLine.dropFirst(4)) - } - } + let contactInfo = parseContactInfoFromVCard(normalizedVCard) var subtitle = "" - if !name.isEmpty { subtitle += String(format: "contact_name".localized, name) + "\n" } - if !phone.isEmpty { subtitle += String(format: "contact_phone".localized, phone) + "\n" } - if !email.isEmpty { subtitle += String(format: "contact_email".localized, email) + "\n" } - if !company.isEmpty { subtitle += String(format: "contact_company".localized, company) + "\n" } - if !title.isEmpty { subtitle += String(format: "contact_title".localized, title) + "\n" } - if !address.isEmpty { subtitle += String(format: "contact_address".localized, address) + "\n" } - if !website.isEmpty { subtitle += String(format: "contact_website".localized, website) + "\n" } + if !contactInfo.name.isEmpty { subtitle += String(format: "contact_name".localized, contactInfo.name) + "\n" } + if !contactInfo.phoneNumber.isEmpty { subtitle += String(format: "contact_phone".localized, contactInfo.phoneNumber) + "\n" } + if !contactInfo.email.isEmpty { subtitle += String(format: "contact_email".localized, contactInfo.email) + "\n" } + if !contactInfo.organization.isEmpty { subtitle += String(format: "contact_company".localized, contactInfo.organization) + "\n" } + if !contactInfo.title.isEmpty { subtitle += String(format: "contact_title".localized, contactInfo.title) + "\n" } + if !contactInfo.address.isEmpty { subtitle += String(format: "contact_address".localized, contactInfo.address) + "\n" } // 移除最后一个换行符 if subtitle.hasSuffix("\n") { subtitle = String(subtitle.dropLast()) } + // 存储联系人信息到额外数据中 + let extraData = try? JSONEncoder().encode(contactInfo) + return ParsedQRData( type: .vcard, title: "contact_information".localized, subtitle: subtitle, - icon: "person.crop.rectangle" + icon: "person.crop.rectangle", + extraData: extraData ) } // MARK: - 解析MeCard private static func parseMeCard(_ content: String) -> ParsedQRData { - let mecardInfo = content.replacingOccurrences(of: "MECARD:", with: "") - let components = mecardInfo.components(separatedBy: ";") - - var name = "" - var nickname = "" - var phone = "" - var email = "" - var company = "" - var address = "" - var website = "" - var birthday = "" - var note = "" - - for component in components { - let trimmedComponent = component.trimmingCharacters(in: .whitespaces) - if trimmedComponent.isEmpty { continue } - - if trimmedComponent.hasPrefix("N:") { - let nameValue = String(trimmedComponent.dropFirst(2)) - let nameParts = nameValue.components(separatedBy: ",") - if nameParts.count >= 2 { - let lastName = nameParts[0] - let firstName = nameParts[1] - name = "\(firstName) \(lastName)" - } else if nameParts.count == 1 { - name = nameParts[0] - } - } else if trimmedComponent.hasPrefix("NICKNAME:") { - nickname = String(trimmedComponent.dropFirst(9)) - } else if trimmedComponent.hasPrefix("TEL:") { - phone = String(trimmedComponent.dropFirst(4)) - } else if trimmedComponent.hasPrefix("EMAIL:") { - email = String(trimmedComponent.dropFirst(6)) - } else if trimmedComponent.hasPrefix("ORG:") { - company = String(trimmedComponent.dropFirst(4)) - } else if trimmedComponent.hasPrefix("ADR:") { - address = String(trimmedComponent.dropFirst(4)) - } else if trimmedComponent.hasPrefix("URL:") { - website = String(trimmedComponent.dropFirst(4)) - } else if trimmedComponent.hasPrefix("BDAY:") { - let birthdayValue = String(trimmedComponent.dropFirst(5)) - if birthdayValue.count == 8 { - let year = String(birthdayValue.prefix(4)) - let month = String(birthdayValue.dropFirst(4).prefix(2)) - let day = String(birthdayValue.dropFirst(6)) - birthday = String(format: "birthday_format".localized, year, month, day) - } else { - birthday = birthdayValue - } - } else if trimmedComponent.hasPrefix("NOTE:") { - note = String(trimmedComponent.dropFirst(5)) - } - } + let contactInfo = parseContactInfoFromMeCard(content) var subtitle = "" - if !name.isEmpty { subtitle += String(format: "contact_name".localized, name) + "\n" } - if !nickname.isEmpty { subtitle += String(format: "contact_nickname".localized, nickname) + "\n" } - if !phone.isEmpty { subtitle += String(format: "contact_phone".localized, phone) + "\n" } - if !email.isEmpty { subtitle += String(format: "contact_email".localized, email) + "\n" } - if !company.isEmpty { subtitle += String(format: "contact_company".localized, company) + "\n" } - if !address.isEmpty { subtitle += String(format: "contact_address".localized, address) + "\n" } - if !website.isEmpty { subtitle += String(format: "contact_website".localized, website) + "\n" } - if !birthday.isEmpty { subtitle += String(format: "contact_birthday".localized, birthday) + "\n" } - if !note.isEmpty { subtitle += String(format: "contact_note".localized, note) + "\n" } + if !contactInfo.name.isEmpty { subtitle += String(format: "contact_name".localized, contactInfo.name) + "\n" } + if !contactInfo.phoneNumber.isEmpty { subtitle += String(format: "contact_phone".localized, contactInfo.phoneNumber) + "\n" } + if !contactInfo.email.isEmpty { subtitle += String(format: "contact_email".localized, contactInfo.email) + "\n" } + if !contactInfo.organization.isEmpty { subtitle += String(format: "contact_company".localized, contactInfo.organization) + "\n" } + if !contactInfo.title.isEmpty { subtitle += String(format: "contact_title".localized, contactInfo.title) + "\n" } + if !contactInfo.address.isEmpty { subtitle += String(format: "contact_address".localized, contactInfo.address) + "\n" } // 移除最后一个换行符 if subtitle.hasSuffix("\n") { subtitle = String(subtitle.dropLast()) } + // 存储联系人信息到额外数据中 + let extraData = try? JSONEncoder().encode(contactInfo) + return ParsedQRData( type: .mecard, title: "contact_information".localized, subtitle: subtitle, - icon: "person.crop.rectangle" + icon: "person.crop.rectangle", + extraData: extraData ) } @@ -466,16 +430,17 @@ class QRCodeParser { var description = "" for line in lines { - if line.hasPrefix("SUMMARY:") { - summary = String(line.dropFirst(8)) - } else if line.hasPrefix("DTSTART:") { - startTime = String(line.dropFirst(8)) - } else if line.hasPrefix("DTEND:") { - endTime = String(line.dropFirst(6)) - } else if line.hasPrefix("LOCATION:") { - location = String(line.dropFirst(9)) - } else if line.hasPrefix("DESCRIPTION:") { - description = String(line.dropFirst(12)) + let trimmedLine = line.trimmingCharacters(in: .whitespaces) + if trimmedLine.uppercased().hasPrefix("SUMMARY:") { + summary = String(trimmedLine.dropFirst(8)).trimmingCharacters(in: .whitespaces) + } else if trimmedLine.uppercased().hasPrefix("DTSTART:") { + startTime = String(trimmedLine.dropFirst(8)).trimmingCharacters(in: .whitespaces) + } else if trimmedLine.uppercased().hasPrefix("DTEND:") { + endTime = String(trimmedLine.dropFirst(6)).trimmingCharacters(in: .whitespaces) + } else if trimmedLine.uppercased().hasPrefix("LOCATION:") { + location = String(trimmedLine.dropFirst(9)).trimmingCharacters(in: .whitespaces) + } else if trimmedLine.uppercased().hasPrefix("DESCRIPTION:") { + description = String(trimmedLine.dropFirst(12)).trimmingCharacters(in: .whitespaces) } } @@ -484,42 +449,89 @@ class QRCodeParser { let formattedEndTime = formatCalendarTime(endTime) let title = "calendar_event".localized - var subtitle = String(format: "calendar_event_info".localized, summary, formattedStartTime, formattedEndTime) + var subtitle = "" + + // 构建更美观的副标题 + if !summary.isEmpty { + subtitle += summary + } + + if !formattedStartTime.isEmpty { + if !subtitle.isEmpty { subtitle += "\n" } + subtitle += String(format: "📅 %@", formattedStartTime) + } + + if !formattedEndTime.isEmpty && formattedEndTime != formattedStartTime { + subtitle += String(format: " - %@", formattedEndTime) + } + if !location.isEmpty { - subtitle += String(format: "calendar_event_location".localized, location) + if !subtitle.isEmpty { subtitle += "\n" } + subtitle += String(format: "📍 %@", location) } + if !description.isEmpty { - subtitle += String(format: "calendar_event_description".localized, description) + if !subtitle.isEmpty { subtitle += "\n" } + let truncatedDescription = description.count > 100 ? String(description.prefix(100)) + "..." : description + subtitle += String(format: "📝 %@", truncatedDescription) } + // 存储日历详细信息到额外数据中 + let calendarDetails = CalendarDetails(summary: summary, startTime: startTime, endTime: endTime, location: location, description: description) + let extraData = try? JSONEncoder().encode(calendarDetails) + return ParsedQRData( type: .calendar, title: title, subtitle: subtitle, - icon: "calendar" + icon: "calendar", + extraData: extraData ) } // MARK: - 格式化日历时间 private static func formatCalendarTime(_ timeString: String) -> String { - guard timeString.count >= 15 else { return timeString } + guard !timeString.isEmpty else { return "" } let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss" - if let date = dateFormatter.date(from: timeString) { - let displayFormatter = DateFormatter() - displayFormatter.dateStyle = .medium - displayFormatter.timeStyle = .short - return displayFormatter.string(from: date) + // 尝试不同的时间格式 + let formats = [ + "yyyyMMdd'T'HHmmss", // 标准格式:20241201T140000 + "yyyyMMdd'T'HHmmss'Z'", // 带Z的格式:20241201T140000Z + "yyyyMMdd", // 仅日期:20241201 + "yyyyMMdd'T'HHmm", // 无秒:20241201T1400 + "yyyy-MM-dd'T'HH:mm:ss", // ISO格式:2024-12-01T14:00:00 + "yyyy-MM-dd'T'HH:mm:ss'Z'" // ISO格式带Z:2024-12-01T14:00:00Z + ] + + for format in formats { + dateFormatter.dateFormat = format + if let date = dateFormatter.date(from: timeString) { + let displayFormatter = DateFormatter() + displayFormatter.dateStyle = .medium + displayFormatter.timeStyle = .short + return displayFormatter.string(from: date) + } } + // 如果无法解析,返回原始字符串 return timeString } // MARK: - 解析Instagram private static func parseInstagram(_ content: String) -> ParsedQRData { - let username = content.replacingOccurrences(of: "instagram://user?username=", with: "") + var username = "" + + if content.lowercased().hasPrefix("instagram://user?username=") { + username = content.replacingOccurrences(of: "instagram://user?username=", with: "", options: .caseInsensitive) + } else if content.lowercased().contains("instagram.com") { + // 从网页URL中提取用户名 + let components = content.components(separatedBy: "/") + if let lastComponent = components.last, !lastComponent.isEmpty && !lastComponent.contains("?") { + username = lastComponent + } + } return ParsedQRData( type: .instagram, @@ -531,7 +543,17 @@ class QRCodeParser { // MARK: - 解析Facebook private static func parseFacebook(_ content: String) -> ParsedQRData { - let profileId = content.replacingOccurrences(of: "fb://profile/", with: "") + var profileId = "" + + if content.lowercased().hasPrefix("fb://profile/") { + profileId = content.replacingOccurrences(of: "fb://profile/", with: "", options: .caseInsensitive) + } else if content.lowercased().contains("facebook.com") { + // 从网页URL中提取用户ID + let components = content.components(separatedBy: "/") + if let lastComponent = components.last, !lastComponent.isEmpty && !lastComponent.contains("?") { + profileId = lastComponent + } + } return ParsedQRData( type: .facebook, @@ -543,12 +565,44 @@ class QRCodeParser { // MARK: - 解析Spotify private static func parseSpotify(_ content: String) -> ParsedQRData { - let searchQuery = content.replacingOccurrences(of: "spotify:search:", with: "") + var subtitle = "" + + if content.lowercased().hasPrefix("spotify:search:") { + let searchQuery = content.replacingOccurrences(of: "spotify:search:", with: "", options: .caseInsensitive) + subtitle = String(format: "spotify_search_query".localized, searchQuery) + } else if content.lowercased().hasPrefix("spotify:track:") { + let trackId = content.replacingOccurrences(of: "spotify:track:", with: "", options: .caseInsensitive) + subtitle = String(format: "spotify_track".localized, trackId) + } else if content.lowercased().hasPrefix("spotify:album:") { + let albumId = content.replacingOccurrences(of: "spotify:album:", with: "", options: .caseInsensitive) + subtitle = String(format: "spotify_album".localized, albumId) + } else if content.lowercased().hasPrefix("spotify:artist:") { + let artistId = content.replacingOccurrences(of: "spotify:artist:", with: "", options: .caseInsensitive) + subtitle = String(format: "spotify_artist".localized, artistId) + } else if content.lowercased().hasPrefix("spotify:playlist:") { + let playlistId = content.replacingOccurrences(of: "spotify:playlist:", with: "", options: .caseInsensitive) + subtitle = String(format: "spotify_playlist".localized, playlistId) + } else if content.lowercased().contains("open.spotify.com") { + // 从网页URL中提取信息 + if content.lowercased().contains("/track/") { + let trackId = content.components(separatedBy: "/track/").last?.components(separatedBy: "?").first ?? "" + subtitle = String(format: "spotify_track".localized, trackId) + } else if content.lowercased().contains("/album/") { + let albumId = content.components(separatedBy: "/album/").last?.components(separatedBy: "?").first ?? "" + subtitle = String(format: "spotify_album".localized, albumId) + } else if content.lowercased().contains("/artist/") { + let artistId = content.components(separatedBy: "/artist/").last?.components(separatedBy: "?").first ?? "" + subtitle = String(format: "spotify_artist".localized, artistId) + } else if content.lowercased().contains("/playlist/") { + let playlistId = content.components(separatedBy: "/playlist/").last?.components(separatedBy: "?").first ?? "" + subtitle = String(format: "spotify_playlist".localized, playlistId) + } + } return ParsedQRData( type: .spotify, title: "spotify".localized, - subtitle: String(format: "spotify_search_query".localized, searchQuery), + subtitle: subtitle, icon: "music.note" ) } @@ -557,9 +611,9 @@ class QRCodeParser { private static func parseTwitter(_ content: String) -> ParsedQRData { var username = "" - if content.hasPrefix("twitter://user?screen_name=") { - username = content.replacingOccurrences(of: "twitter://user?screen_name=", with: "") - } else if content.contains("x.com") || content.contains("twitter.com") { + if content.lowercased().hasPrefix("twitter://user?screen_name=") { + username = content.replacingOccurrences(of: "twitter://user?screen_name=", with: "", options: .caseInsensitive) + } else if content.lowercased().contains("x.com") || content.lowercased().contains("twitter.com") { username = content.components(separatedBy: "/").dropLast().last ?? "" } @@ -573,7 +627,7 @@ class QRCodeParser { // MARK: - 解析WhatsApp private static func parseWhatsApp(_ content: String) -> ParsedQRData { - let phone = content.replacingOccurrences(of: "whatsapp://send?phone=", with: "") + let phone = content.replacingOccurrences(of: "whatsapp://send?phone=", with: "", options: .caseInsensitive) return ParsedQRData( type: .whatsapp, @@ -585,7 +639,7 @@ class QRCodeParser { // MARK: - 解析Viber private static func parseViber(_ content: String) -> ParsedQRData { - let phone = content.replacingOccurrences(of: "viber://add?number=", with: "") + let phone = content.replacingOccurrences(of: "viber://add?number=", with: "", options: .caseInsensitive) return ParsedQRData( type: .viber, @@ -597,7 +651,7 @@ class QRCodeParser { // MARK: - 解析Snapchat private static func parseSnapchat(_ content: String) -> ParsedQRData { - let username = content.replacingOccurrences(of: "snapchat://", with: "") + let username = content.replacingOccurrences(of: "snapchat://", with: "", options: .caseInsensitive) return ParsedQRData( type: .snapchat, @@ -611,12 +665,12 @@ class QRCodeParser { private static func parseTikTok(_ content: String) -> ParsedQRData { var username = "" - if content.contains("www.tiktok.com") { + if content.lowercased().contains("www.tiktok.com") { // 处理 https://www.tiktok.com/@username 格式 if let atIndex = content.lastIndex(of: "@") { username = String(content[content.index(after: atIndex)...]) } - } else if content.contains("tiktok.com") { + } else if content.lowercased().contains("tiktok.com") { // 处理 https://tiktok.com/@username 格式 if let atIndex = content.lastIndex(of: "@") { username = String(content[content.index(after: atIndex)...]) @@ -660,6 +714,99 @@ class QRCodeParser { ) } + // MARK: - 联系人信息解析方法 + + /// 从vCard内容解析联系人信息 + static func parseContactInfoFromVCard(_ content: String) -> ContactInfo { + let lines = content.components(separatedBy: .newlines) + var contactInfo = ContactInfo() + + for line in lines { + let trimmedLine = line.trimmingCharacters(in: .whitespaces) + if trimmedLine.hasPrefix("FN:") { + contactInfo.name = String(trimmedLine.dropFirst(3)) + } else if trimmedLine.hasPrefix("TEL") { + let telValue = String(trimmedLine.dropFirst(3)) + if telValue.contains(":") { + let number = telValue.components(separatedBy: ":").last ?? "" + contactInfo.phoneNumber = number + } + } else if trimmedLine.hasPrefix("EMAIL") { + let emailValue = String(trimmedLine.dropFirst(5)) + if emailValue.contains(":") { + let emailAddress = emailValue.components(separatedBy: ":").last ?? "" + contactInfo.email = emailAddress + } + } else if trimmedLine.hasPrefix("ORG:") { + contactInfo.organization = String(trimmedLine.dropFirst(4)) + } else if trimmedLine.hasPrefix("TITLE:") { + contactInfo.title = String(trimmedLine.dropFirst(6)) + } else if trimmedLine.hasPrefix("ADR") { + let adrValue = String(trimmedLine.dropFirst(3)) + if adrValue.contains(":") { + let addressParts = adrValue.components(separatedBy: ":") + if addressParts.count > 1 { + let addressComponents = addressParts[1].components(separatedBy: ";") + if addressComponents.count >= 3 { + contactInfo.address = "\(addressComponents[2]) \(addressComponents[1])" + } + } + } + } + } + + return contactInfo + } + + /// 从MeCard内容解析联系人信息 + static func parseContactInfoFromMeCard(_ content: String) -> ContactInfo { + let mecardInfo = content.replacingOccurrences(of: "MECARD:", with: "") + let components = mecardInfo.components(separatedBy: ";") + var contactInfo = ContactInfo() + + for component in components { + let trimmedComponent = component.trimmingCharacters(in: .whitespaces) + if trimmedComponent.isEmpty { continue } + + if trimmedComponent.hasPrefix("N:") { + let nameValue = String(trimmedComponent.dropFirst(2)) + let nameParts = nameValue.components(separatedBy: ",") + if nameParts.count >= 2 { + let lastName = nameParts[0] + let firstName = nameParts[1] + contactInfo.name = "\(firstName) \(lastName)" + } else if nameParts.count == 1 { + contactInfo.name = nameParts[0] + } + } else if trimmedComponent.hasPrefix("TEL:") { + contactInfo.phoneNumber = String(trimmedComponent.dropFirst(4)) + } else if trimmedComponent.hasPrefix("EMAIL:") { + contactInfo.email = String(trimmedComponent.dropFirst(6)) + } else if trimmedComponent.hasPrefix("ORG:") { + contactInfo.organization = String(trimmedComponent.dropFirst(4)) + } else if trimmedComponent.hasPrefix("TITLE:") { + contactInfo.title = String(trimmedComponent.dropFirst(6)) + } else if trimmedComponent.hasPrefix("ADR:") { + contactInfo.address = String(trimmedComponent.dropFirst(4)) + } + } + + return contactInfo + } + + /// 通用联系人信息解析方法 + static func parseContactInfo(from content: String) -> ContactInfo? { + if content.hasPrefix("BEGIN:VCARD") { + let normalizedVCard = VCardConverter.normalizeVCard(content) + let contactInfo = parseContactInfoFromVCard(normalizedVCard) + return (contactInfo.name.isEmpty && contactInfo.phoneNumber.isEmpty) ? nil : contactInfo + } else if content.hasPrefix("MECARD:") { + let contactInfo = parseContactInfoFromMeCard(content) + return (contactInfo.name.isEmpty && contactInfo.phoneNumber.isEmpty) ? nil : contactInfo + } + return nil + } + // MARK: - 验证URL private static func isValidURL(_ string: String) -> Bool { guard let url = URL(string: string) else { return false } diff --git a/MyQrCode/Utils/CalendarManager.swift b/MyQrCode/Utils/CalendarManager.swift new file mode 100644 index 0000000..a0ab3ed --- /dev/null +++ b/MyQrCode/Utils/CalendarManager.swift @@ -0,0 +1,160 @@ +import Foundation +import EventKit +import Combine + +// MARK: - 日历管理器 +class CalendarManager: ObservableObject { + static let shared = CalendarManager() + + private let eventStore = EKEventStore() + @Published var objectWillChange = ObservableObjectPublisher() + + private init() {} + + // MARK: - 添加事件到日历 + func addEventToCalendar(calendarDetails: CalendarDetails, completion: @escaping (Bool, String?) -> Void) { + // 检查权限 + if #available(iOS 17.0, *) { + // iOS 17+ 使用新的权限API + switch EKEventStore.authorizationStatus(for: .event) { + case .fullAccess, .writeOnly: + performAddEvent(calendarDetails: calendarDetails, completion: completion) + case .notDetermined: + requestCalendarAccess { [weak self] granted in + if granted { + self?.performAddEvent(calendarDetails: calendarDetails, completion: completion) + } else { + DispatchQueue.main.async { + completion(false, "calendar_permission_denied".localized) + } + } + } + case .denied, .restricted: + DispatchQueue.main.async { + completion(false, "calendar_permission_denied".localized) + } + @unknown default: + DispatchQueue.main.async { + completion(false, "calendar_permission_unknown".localized) + } + } + } else { + // iOS 16及以下使用旧权限API + switch EKEventStore.authorizationStatus(for: .event) { + case .authorized: + performAddEvent(calendarDetails: calendarDetails, completion: completion) + case .notDetermined: + requestCalendarAccess { [weak self] granted in + if granted { + self?.performAddEvent(calendarDetails: calendarDetails, completion: completion) + } else { + DispatchQueue.main.async { + completion(false, "calendar_permission_denied".localized) + } + } + } + case .denied, .restricted: + DispatchQueue.main.async { + completion(false, "calendar_permission_denied".localized) + } + @unknown default: + DispatchQueue.main.async { + completion(false, "calendar_permission_unknown".localized) + } + } + } + } + + // MARK: - 请求日历权限 + private func requestCalendarAccess(completion: @escaping (Bool) -> Void) { + if #available(iOS 17.0, *) { + Task { + do { + let granted = try await eventStore.requestWriteOnlyAccessToEvents() + DispatchQueue.main.async { + completion(granted) + } + } catch { + DispatchQueue.main.async { + completion(false) + } + } + } + } else { + eventStore.requestAccess(to: .event) { granted, error in + DispatchQueue.main.async { + completion(granted) + } + } + } + } + + // MARK: - 执行添加事件 + private func performAddEvent(calendarDetails: CalendarDetails, completion: @escaping (Bool, String?) -> Void) { + let event = EKEvent(eventStore: eventStore) + + // 设置事件标题 + event.title = calendarDetails.summary.isEmpty ? "calendar_event".localized : calendarDetails.summary + + // 设置事件描述 + if !calendarDetails.description.isEmpty { + event.notes = calendarDetails.description + } + + // 设置事件位置 + if !calendarDetails.location.isEmpty { + event.location = calendarDetails.location + } + + // 设置开始时间 + if let startDate = parseCalendarDate(calendarDetails.startTime) { + event.startDate = startDate + } else { + DispatchQueue.main.async { + completion(false, "calendar_invalid_start_time".localized) + } + return + } + + // 设置结束时间 + if let endDate = parseCalendarDate(calendarDetails.endTime) { + event.endDate = endDate + } else { + // 如果没有结束时间,设置为开始时间后1小时 + event.endDate = event.startDate.addingTimeInterval(3600) + } + + // 设置日历(使用默认日历) + event.calendar = eventStore.defaultCalendarForNewEvents + + // 设置提醒(15分钟前) + let alarm = EKAlarm(relativeOffset: -900) // -15分钟 + event.addAlarm(alarm) + + // 保存事件 + do { + try eventStore.save(event, span: .thisEvent) + DispatchQueue.main.async { + completion(true, nil) + } + } catch { + DispatchQueue.main.async { + completion(false, "calendar_save_failed".localized) + } + } + } + + // MARK: - 解析日历日期 + private func parseCalendarDate(_ dateString: String) -> Date? { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss" + + if let date = dateFormatter.date(from: dateString) { + return date + } + + // 尝试其他格式 + dateFormatter.dateFormat = "yyyyMMdd" + return dateFormatter.date(from: dateString) + } +} diff --git a/MyQrCode/Utils/ContactManager.swift b/MyQrCode/Utils/ContactManager.swift new file mode 100644 index 0000000..eb43133 --- /dev/null +++ b/MyQrCode/Utils/ContactManager.swift @@ -0,0 +1,168 @@ +import Foundation +import Contacts +import UIKit +import MessageUI +import Combine + +class ContactManager: ObservableObject { + static let shared = ContactManager() + + private init() {} + + // MARK: - 添加联系人到通讯录 + func addContactToAddressBook(vcardContent: String, completion: @escaping (Bool, String?) -> Void) { + // 检查通讯录权限 + let status = CNContactStore.authorizationStatus(for: .contacts) + + switch status { + case .authorized: + performAddContact(vcardContent: vcardContent, completion: completion) + case .denied, .restricted: + completion(false, "contact_permission_denied".localized) + case .notDetermined: + requestContactPermission { [weak self] granted in + if granted { + self?.performAddContact(vcardContent: vcardContent, completion: completion) + } else { + completion(false, "contact_permission_denied".localized) + } + } + case .limited: + completion(false, "contact_permission_limited".localized) + @unknown default: + completion(false, "contact_permission_unknown".localized) + } + } + + private func performAddContact(vcardContent: String, completion: @escaping (Bool, String?) -> Void) { + DispatchQueue.global(qos: .userInitiated).async { + do { + let contactStore = CNContactStore() + let data = vcardContent.data(using: .utf8)! + + // 解析vCard数据 + let contacts = try CNContactVCardSerialization.contacts(with: data) + + guard let contact = contacts.first else { + DispatchQueue.main.async { + completion(false, "contact_parse_failed".localized) + } + return + } + + // 创建可变的联系人副本 + let mutableContact = contact.mutableCopy() as! CNMutableContact + + // 创建保存请求 + let saveRequest = CNSaveRequest() + saveRequest.add(mutableContact, toContainerWithIdentifier: nil) + + // 保存联系人 + try contactStore.execute(saveRequest) + + DispatchQueue.main.async { + completion(true, "contact_added_successfully".localized) + } + + } catch { + DispatchQueue.main.async { + completion(false, "contact_add_failed".localized) + } + } + } + } + + private func requestContactPermission(completion: @escaping (Bool) -> Void) { + let contactStore = CNContactStore() + contactStore.requestAccess(for: .contacts) { granted, error in + DispatchQueue.main.async { + completion(granted) + } + } + } + + // MARK: - 打电话 + func makePhoneCall(phoneNumber: String, completion: @escaping (Bool, String?) -> Void) { + // 清理电话号码(移除空格、括号等) + let cleanNumber = phoneNumber.replacingOccurrences(of: "[^0-9+]", with: "", options: .regularExpression) + + guard let url = URL(string: "tel://\(cleanNumber)") else { + completion(false, "invalid_phone_number".localized) + return + } + + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) { success in + DispatchQueue.main.async { + if success { + completion(true, "phone_call_initiated".localized) + } else { + completion(false, "phone_call_failed".localized) + } + } + } + } else { + completion(false, "phone_call_not_supported".localized) + } + } + + // MARK: - 发短信 + func sendSMS(phoneNumber: String, message: String = "", completion: @escaping (Bool, String?) -> Void) { + // 清理电话号码 + let cleanNumber = phoneNumber.replacingOccurrences(of: "[^0-9+]", with: "", options: .regularExpression) + + guard let url = URL(string: "sms://\(cleanNumber)?body=\(message.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")") else { + completion(false, "invalid_phone_number".localized) + return + } + + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) { success in + DispatchQueue.main.async { + if success { + completion(true, "sms_app_opened".localized) + } else { + completion(false, "sms_app_failed".localized) + } + } + } + } else { + completion(false, "sms_app_not_supported".localized) + } + } + + // MARK: - 解析联系人信息(已迁移到QRCodeParser) + // 此方法已废弃,请使用 QRCodeParser.parseContactInfo(from:) + @available(*, deprecated, message: "请使用 QRCodeParser.parseContactInfo(from:)") + func parseContactInfo(from content: String) -> ContactInfo? { + return QRCodeParser.parseContactInfo(from: content) + } +} + +// MARK: - 联系人信息结构体 +struct ContactInfo: Codable { + var name: String = "" + var phoneNumber: String = "" + var email: String = "" + var organization: String = "" + var title: String = "" + var address: String = "" + + var displayName: String { + if !name.isEmpty { + return name + } else if !organization.isEmpty { + return organization + } else { + return "contact".localized + } + } + + var hasPhoneNumber: Bool { + return !phoneNumber.isEmpty + } + + var hasEmail: Bool { + return !email.isEmpty + } +} diff --git a/MyQrCode/Views/History/QRCodeDetailView.swift b/MyQrCode/Views/History/QRCodeDetailView.swift index b2ebb21..c31e166 100644 --- a/MyQrCode/Views/History/QRCodeDetailView.swift +++ b/MyQrCode/Views/History/QRCodeDetailView.swift @@ -16,9 +16,6 @@ struct QRCodeDetailView: View { var body: some View { ScrollView { VStack(spacing: 20) { - // 二维码图片 - qrCodeStyleSection - // 解析后的详细信息 parsedInfoSection @@ -32,7 +29,8 @@ struct QRCodeDetailView: View { // Decorate code按钮 decorateCodeButton } - .padding() + .padding(.horizontal, 16) + .padding(.vertical, 12) } .navigationTitle(getNavigationTitle()) .navigationBarTitleDisplayMode(.inline) @@ -117,6 +115,77 @@ Button("confirm".localized) { } Spacer() } + // 根据类型显示不同的详细信息 + if parsedData.type == .vcard || parsedData.type == .mecard { + // 联系人信息详细显示 + contactInfoDetailView(parsedData: parsedData) + } else if parsedData.type == .mail { + // Email信息详细显示 + emailInfoDetailView(parsedData: parsedData) + } else if parsedData.type == .calendar { + // Calendar信息详细显示 + calendarInfoDetailView(parsedData: parsedData) + } else if parsedData.type == .sms { + // SMS信息详细显示 + smsInfoDetailView(parsedData: parsedData) + } else { + // 其他类型的标准显示 + VStack(alignment: .leading, spacing: 8) { + if let subtitle = parsedData.subtitle { + Text(subtitle) + .font(.body) + .foregroundColor(.secondary) + .multilineTextAlignment(.leading) + .frame(maxWidth: .infinity, alignment: .leading) + } + } + .padding() + .background(Color.green.opacity(0.1)) + .cornerRadius(8) + } + } + } + .padding() + .background(Color(.systemBackground)) + .cornerRadius(12) + .shadow(radius: 2) + } + + // MARK: - 联系人信息详细视图 + private func contactInfoDetailView(parsedData: ParsedQRData) -> some View { + VStack(alignment: .leading, spacing: 12) { + if let contactInfo = getContactInfoFromParsedData(parsedData) { + // 姓名 + if !contactInfo.name.isEmpty { + contactInfoRow(icon: "person.fill", title: "name".localized, value: contactInfo.name) + } + + // 电话 + if !contactInfo.phoneNumber.isEmpty { + contactInfoRow(icon: "phone.fill", title: "phone".localized, value: contactInfo.phoneNumber) + } + + // 邮箱 + if !contactInfo.email.isEmpty { + contactInfoRow(icon: "envelope.fill", title: "email".localized, value: contactInfo.email) + } + + // 公司 + if !contactInfo.organization.isEmpty { + contactInfoRow(icon: "building.2.fill", title: "organization".localized, value: contactInfo.organization) + } + + // 职位 + if !contactInfo.title.isEmpty { + contactInfoRow(icon: "briefcase.fill", title: "title".localized, value: contactInfo.title) + } + + // 地址 + if !contactInfo.address.isEmpty { + contactInfoRow(icon: "location.fill", title: "address".localized, value: contactInfo.address) + } + } else { + // 如果无法解析联系人信息,显示原始内容 VStack(alignment: .leading, spacing: 8) { if let subtitle = parsedData.subtitle { Text(subtitle) @@ -131,10 +200,150 @@ Button("confirm".localized) { } .cornerRadius(8) } } - .padding() - .background(Color(.systemBackground)) - .cornerRadius(12) - .shadow(radius: 2) + } + + // MARK: - Email信息详细视图 + private func emailInfoDetailView(parsedData: ParsedQRData) -> some View { + VStack(alignment: .leading, spacing: 12) { + if let emailDetails = getEmailDetails(parsedData: parsedData) { + // 邮箱地址 + if !emailDetails.emailAddress.isEmpty { + contactInfoRow(icon: "envelope.fill", title: "email_address".localized, value: emailDetails.emailAddress) + } + + // 主题 + if !emailDetails.subject.isEmpty { + contactInfoRow(icon: "text.bubble.fill", title: "email_subject".localized, value: emailDetails.subject) + } + + // 内容 + if !emailDetails.body.isEmpty { + contactInfoRow(icon: "doc.text.fill", title: "email_body".localized, value: emailDetails.body) + } + } else { + // 如果无法解析Email信息,显示原始内容 + VStack(alignment: .leading, spacing: 8) { + if let subtitle = parsedData.subtitle { + Text(subtitle) + .font(.body) + .foregroundColor(.secondary) + .multilineTextAlignment(.leading) + .frame(maxWidth: .infinity, alignment: .leading) + } + } + .padding() + .background(Color.blue.opacity(0.1)) + .cornerRadius(8) + } + } + } + + // MARK: - Calendar信息详细视图 + private func calendarInfoDetailView(parsedData: ParsedQRData) -> some View { + VStack(alignment: .leading, spacing: 12) { + if let calendarDetails = getCalendarDetails(parsedData: parsedData) { + // 事件标题 + if !calendarDetails.summary.isEmpty { + contactInfoRow(icon: "calendar.badge.plus", title: "calendar_event_title".localized, value: calendarDetails.summary) + } + + // 开始时间 + if !calendarDetails.startTime.isEmpty { + let formattedStartTime = formatCalendarTime(calendarDetails.startTime) + contactInfoRow(icon: "clock.fill", title: "calendar_start_time".localized, value: formattedStartTime) + } + + // 结束时间 + if !calendarDetails.endTime.isEmpty { + let formattedEndTime = formatCalendarTime(calendarDetails.endTime) + contactInfoRow(icon: "clock.badge.checkmark.fill", title: "calendar_end_time".localized, value: formattedEndTime) + } + + // 地点 + if !calendarDetails.location.isEmpty { + contactInfoRow(icon: "location.fill", title: "calendar_location".localized, value: calendarDetails.location) + } + + // 描述 + if !calendarDetails.description.isEmpty { + contactInfoRow(icon: "text.bubble.fill", title: "calendar_description".localized, value: calendarDetails.description) + } + } else { + // 如果无法解析Calendar信息,显示原始内容 + VStack(alignment: .leading, spacing: 8) { + if let subtitle = parsedData.subtitle { + Text(subtitle) + .font(.body) + .foregroundColor(.secondary) + .multilineTextAlignment(.leading) + .frame(maxWidth: .infinity, alignment: .leading) + } + } + .padding() + .background(Color.orange.opacity(0.1)) + .cornerRadius(8) + } + } + } + + // MARK: - SMS信息详细视图 + private func smsInfoDetailView(parsedData: ParsedQRData) -> some View { + VStack(alignment: .leading, spacing: 12) { + if let smsDetails = getSMSDetails(parsedData: parsedData) { + // 电话号码 + if !smsDetails.phoneNumber.isEmpty { + contactInfoRow(icon: "phone.fill", title: "sms_phone_number".localized, value: smsDetails.phoneNumber) + } + + // 短信内容 + if !smsDetails.message.isEmpty { + contactInfoRow(icon: "message.fill", title: "sms_message".localized, value: smsDetails.message) + } + } else { + // 如果无法解析SMS信息,显示原始内容 + VStack(alignment: .leading, spacing: 8) { + if let subtitle = parsedData.subtitle { + Text(subtitle) + .font(.body) + .foregroundColor(.secondary) + .multilineTextAlignment(.leading) + .frame(maxWidth: .infinity, alignment: .leading) + } + } + .padding() + .background(Color.purple.opacity(0.1)) + .cornerRadius(8) + } + } + } + + // MARK: - 联系人信息行 + private func contactInfoRow(icon: String, title: String, value: String) -> some View { + HStack(alignment: .top, spacing: 12) { + Image(systemName: icon) + .font(.system(size: 16, weight: .medium)) + .foregroundColor(.blue) + .frame(width: 20, height: 20) + .padding(.top, 2) + + VStack(alignment: .leading, spacing: 4) { + Text(title) + .font(.caption) + .foregroundColor(.secondary) + .textCase(.uppercase) + + Text(value) + .font(.body) + .foregroundColor(.primary) + .multilineTextAlignment(.leading) + } + + Spacer() + } + .padding(.vertical, 8) + .padding(.horizontal, 16) + .background(Color(.systemGray6)) + .cornerRadius(8) } // MARK: - 原始内容 @@ -233,66 +442,211 @@ Button("confirm".localized) { } .padding() } + // MARK: - 操作按钮辅助函数 + private func actionButton(icon: String, title: String, color: Color, action: @escaping () -> Void) -> some View { + Button(action: action) { + VStack(spacing: 6) { + Image(systemName: icon) + .font(.system(size: 22, weight: .semibold)) + .foregroundColor(color) + Text(title) + .font(.caption) + .foregroundColor(color) + .lineLimit(2) + .multilineTextAlignment(.center) + } + .frame(width: 70, height: 70) + .background(color.opacity(0.08)) + .cornerRadius(16) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(color.opacity(0.2), lineWidth: 1) + ) + } + .buttonStyle(PlainButtonStyle()) + } + // MARK: - 操作按钮 private var actionButtonsSection: some View { - HStack(spacing: 16) { - // 收藏按钮 - Button(action: toggleFavorite) { - Image(systemName: historyItem.isFavorite ? "heart.fill" : "heart") - .font(.system(size: 24, weight: .semibold)) - .foregroundColor(historyItem.isFavorite ? .red : .gray) - .frame(width: 60, height: 60) - .background(historyItem.isFavorite ? Color.red.opacity(0.1) : Color.gray.opacity(0.1)) - .cornerRadius(12) - } - - // 复制内容按钮 - Button(action: copyContent) { - Image(systemName: "doc.on.doc") - .font(.system(size: 24, weight: .semibold)) - .foregroundColor(.blue) - .frame(width: 60, height: 60) - .background(Color.blue.opacity(0.1)) - .cornerRadius(12) - } - - // WiFi特定按钮 + Group { if let content = historyItem.content { let parsedData = QRCodeParser.parseQRCode(content) - if parsedData.type == .wifi { - // 复制WiFi密码按钮 - Button(action: copyWiFiPassword) { - Image(systemName: "doc.on.doc.fill") - .font(.system(size: 24, weight: .semibold)) - .foregroundColor(.orange) - .frame(width: 60, height: 60) - .background(Color.orange.opacity(0.1)) - .cornerRadius(12) - } + + // 使用流布局来排列按钮 + FlowLayoutView(spacing: 20) { + // 通用按钮 + actionButton( + icon: historyItem.isFavorite ? "heart.fill" : "heart", + title: "favorite".localized, + color: historyItem.isFavorite ? .red : .gray, + action: toggleFavorite + ) - // 设置WiFi按钮 - Button(action: setupWiFi) { - Image(systemName: "link") - .font(.system(size: 24, weight: .semibold)) - .foregroundColor(.purple) - .frame(width: 60, height: 60) - .background(Color.purple.opacity(0.1)) - .cornerRadius(12) - } - } else if canOpenURL(content) { - // 打开链接按钮(如果是URL类型) - Button(action: { openURL(content) }) { - Image(systemName: "arrow.up.right.square") - .font(.system(size: 24, weight: .semibold)) - .foregroundColor(.green) - .frame(width: 60, height: 60) - .background(Color.green.opacity(0.1)) - .cornerRadius(12) + actionButton( + icon: "doc.on.doc", + title: "copy".localized, + color: .blue, + action: copyContent + ) + + // 根据QR码类型添加特定按钮 + if parsedData.type == .wifi { + actionButton( + icon: "doc.on.doc.fill", + title: "copy_password".localized, + color: .orange, + action: copyWiFiPassword + ) + + actionButton( + icon: "link", + title: "connect_wifi".localized, + color: .purple, + action: setupWiFi + ) + } else if parsedData.type == .vcard || parsedData.type == .mecard { + let contactInfo = getContactInfoFromParsedData(parsedData) + + actionButton( + icon: "person.badge.plus", + title: "add_contact".localized, + color: .blue, + action: addContact + ) + + if let contactInfo = contactInfo, contactInfo.hasPhoneNumber { + actionButton( + icon: "phone", + title: "call".localized, + color: .green, + action: { makePhoneCall(phoneNumber: contactInfo.phoneNumber) } + ) + + actionButton( + icon: "message", + title: "send_sms".localized, + color: .purple, + action: { sendSMS(phoneNumber: contactInfo.phoneNumber) } + ) + } + } else if parsedData.type == .phone { + // 从电话URL中提取电话号码 + let phoneNumber = content.replacingOccurrences(of: "tel:", with: "", options: .caseInsensitive) + + if !phoneNumber.isEmpty { + actionButton( + icon: "phone", + title: "call".localized, + color: .green, + action: { makePhoneCall(phoneNumber: phoneNumber) } + ) + + actionButton( + icon: "message", + title: "send_sms".localized, + color: .purple, + action: { sendSMS(phoneNumber: phoneNumber) } + ) + } + } else if parsedData.type == .mail { + // 从邮件URL中提取邮箱地址 + let emailAddress = content.replacingOccurrences(of: "mailto:", with: "", options: .caseInsensitive) + + if !emailAddress.isEmpty { + actionButton( + icon: "envelope", + title: "send_email".localized, + color: .blue, + action: { sendEmail(emailAddress: emailAddress) } + ) + } + } else if parsedData.type == .sms { + let smsDetails = getSMSDetails() + + if let smsDetails = smsDetails, !smsDetails.phoneNumber.isEmpty { + actionButton( + icon: "message", + title: "send_sms".localized, + color: .purple, + action: { sendSMS(phoneNumber: smsDetails.phoneNumber, message: smsDetails.message) } + ) + } + } else if parsedData.type == .calendar { + actionButton( + icon: "calendar.badge.plus", + title: "add_to_calendar".localized, + color: .orange, + action: addEventToCalendar + ) + } else if parsedData.type == .instagram { + actionButton( + icon: "camera", + title: "open".localized, + color: .purple, + action: { openSocialApp(content: content, appType: .instagram) } + ) + } else if parsedData.type == .facebook { + actionButton( + icon: "person.2", + title: "open".localized, + color: .blue, + action: { openSocialApp(content: content, appType: .facebook) } + ) + } else if parsedData.type == .twitter { + actionButton( + icon: "bird", + title: "open".localized, + color: .black, + action: { openSocialApp(content: content, appType: .twitter) } + ) + } else if parsedData.type == .whatsapp { + actionButton( + icon: "message.circle", + title: "open".localized, + color: .green, + action: { openSocialApp(content: content, appType: .whatsapp) } + ) + } else if parsedData.type == .viber { + actionButton( + icon: "message.circle.fill", + title: "open".localized, + color: .purple, + action: { openSocialApp(content: content, appType: .viber) } + ) + } else if parsedData.type == .spotify { + actionButton( + icon: "music.note", + title: "open".localized, + color: .green, + action: { openSocialApp(content: content, appType: .spotify) } + ) + } else if canOpenURL(content) { + actionButton( + icon: "arrow.up.right.square", + title: "open_link".localized, + color: .green, + action: { openURL(content) } + ) } } + } else { + // 没有内容时只显示通用按钮 + FlowLayoutView(spacing: 20) { + actionButton( + icon: historyItem.isFavorite ? "heart.fill" : "heart", + title: "favorite".localized, + color: historyItem.isFavorite ? .red : .gray, + action: toggleFavorite + ) + + actionButton( + icon: "doc.on.doc", + title: "copy".localized, + color: .blue, + action: copyContent + ) + } } - - Spacer() } .padding() .background(Color(.systemBackground)) @@ -364,6 +718,28 @@ Button("confirm".localized) { } return try? JSONDecoder().decode(WiFiDetails.self, from: extraData) } + // MARK: - 获取SMS详细信息 + private func getSMSDetails() -> SMSDetails? { + guard let content = historyItem.content else { return nil } + let parsedData = QRCodeParser.parseQRCode(content) + + guard parsedData.type == .sms, + let extraData = parsedData.extraData else { return nil } + + return try? JSONDecoder().decode(SMSDetails.self, from: extraData) + } + + // MARK: - 获取日历详细信息 + private func getCalendarDetails() -> CalendarDetails? { + guard let content = historyItem.content else { return nil } + let parsedData = QRCodeParser.parseQRCode(content) + + guard parsedData.type == .calendar, + let extraData = parsedData.extraData else { return nil } + + return try? JSONDecoder().decode(CalendarDetails.self, from: extraData) + } + // MARK: - 复制WiFi密码 private func copyWiFiPassword() { guard let wifiDetails = getWiFiDetails() else { return } @@ -390,6 +766,142 @@ Button("confirm".localized) { } } } + // MARK: - 添加联系人 + private func addContact() { + guard let content = historyItem.content else { return } + + ContactManager.shared.addContactToAddressBook(vcardContent: content) { success, error in + DispatchQueue.main.async { + if success { + self.alertMessage = "contact_added_successfully".localized + } else { + self.alertMessage = error ?? "contact_add_failed".localized + } + self.showingAlert = true + } + } + } + + // MARK: - 打电话 + private func makePhoneCall(phoneNumber: String) { + ContactManager.shared.makePhoneCall(phoneNumber: phoneNumber) { success, error in + DispatchQueue.main.async { + if !success { + self.alertMessage = error ?? "phone_call_failed".localized + self.showingAlert = true + } + } + } + } + + // MARK: - 发短信 + private func sendSMS(phoneNumber: String, message: String = "") { + ContactManager.shared.sendSMS(phoneNumber: phoneNumber, message: message) { success, error in + DispatchQueue.main.async { + if !success { + self.alertMessage = error ?? "sms_app_failed".localized + self.showingAlert = true + } + } + } + } + + // MARK: - 发送邮件 + private func sendEmail(emailAddress: String) { + let mailtoURL = "mailto:\(emailAddress)" + if let url = URL(string: mailtoURL), UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) + } else { + alertMessage = "email_app_failed".localized + showingAlert = true + } + } + + // MARK: - 添加事件到日历 + private func addEventToCalendar() { + guard let calendarDetails = getCalendarDetails() else { return } + + CalendarManager.shared.addEventToCalendar(calendarDetails: calendarDetails) { success, error in + DispatchQueue.main.async { + if success { + self.alertMessage = "calendar_event_added_successfully".localized + } else { + self.alertMessage = error ?? "calendar_event_add_failed".localized + } + self.showingAlert = true + } + } + } + + // MARK: - 从解析数据中获取联系人信息 + private func getContactInfoFromParsedData(_ parsedData: ParsedQRData) -> ContactInfo? { + guard let extraData = parsedData.extraData else { return nil } + + do { + let contactInfo = try JSONDecoder().decode(ContactInfo.self, from: extraData) + return contactInfo + } catch { + print("解析联系人信息失败: \(error)") + return nil + } + } + + // MARK: - 从解析数据中获取Email信息 + private func getEmailDetails(parsedData: ParsedQRData) -> EmailDetails? { + guard let extraData = parsedData.extraData else { return nil } + + do { + let emailDetails = try JSONDecoder().decode(EmailDetails.self, from: extraData) + return emailDetails + } catch { + print("解析Email信息失败: \(error)") + return nil + } + } + + // MARK: - 从解析数据中获取Calendar信息 + private func getCalendarDetails(parsedData: ParsedQRData) -> CalendarDetails? { + guard let extraData = parsedData.extraData else { return nil } + + do { + let calendarDetails = try JSONDecoder().decode(CalendarDetails.self, from: extraData) + return calendarDetails + } catch { + print("解析Calendar信息失败: \(error)") + return nil + } + } + + // MARK: - 从解析数据中获取SMS信息 + private func getSMSDetails(parsedData: ParsedQRData) -> SMSDetails? { + guard let extraData = parsedData.extraData else { return nil } + + do { + let smsDetails = try JSONDecoder().decode(SMSDetails.self, from: extraData) + return smsDetails + } catch { + print("解析SMS信息失败: \(error)") + return nil + } + } + + // MARK: - 格式化日历时间 + private func formatCalendarTime(_ timeString: String) -> String { + guard timeString.count >= 15 else { return timeString } + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss" + + if let date = dateFormatter.date(from: timeString) { + let displayFormatter = DateFormatter() + displayFormatter.dateStyle = .medium + displayFormatter.timeStyle = .short + return displayFormatter.string(from: date) + } + + return timeString + } + } @@ -459,6 +971,18 @@ struct ShareSheet: UIViewControllerRepresentable { NavigationView { QRCodeDetailView(historyItem: item) } } +#Preview("Calendar") { + let ctx = PreviewData.context + let item = PreviewData.calendarSample(in: ctx) + NavigationView { QRCodeDetailView(historyItem: item) } +} + +#Preview("Email") { + let ctx = PreviewData.context + let item = PreviewData.emailSample(in: ctx) + NavigationView { QRCodeDetailView(historyItem: item) } +} + // MARK: - Preview Data private enum PreviewData { static let context: NSManagedObjectContext = { @@ -538,6 +1062,24 @@ private enum PreviewData { let content = "MECARD:N:Doe,John;NICKNAME:Johnny;TEL:+1234567890;EMAIL:john.doe@example.com;ORG:Example Company;TITLE:Software Engineer;ADR:123 Main St,Anytown,CA,12345,USA;URL:https://example.com;NOTE:This is a note;" return makeBaseItem(in: context, content: content, qrType: .mecard) } + + static func calendarSample(in context: NSManagedObjectContext) -> HistoryItem { + let content = """ + BEGIN:VEVENT + SUMMARY:团队会议 + DTSTART:20241201T140000 + DTEND:20241201T150000 + LOCATION:会议室A + DESCRIPTION:讨论项目进度和下一步计划 + END:VEVENT + """.trimmingCharacters(in: .whitespacesAndNewlines) + return makeBaseItem(in: context, content: content, qrType: .calendar) + } + + static func emailSample(in context: NSManagedObjectContext) -> HistoryItem { + let content = "mailto:example@email.com?subject=Hello&body=This is a test email message with some content to demonstrate the email QR code functionality." + return makeBaseItem(in: context, content: content, qrType: .mail) + } } // MARK: - 样式信息辅助方法 @@ -735,6 +1277,10 @@ case .silver: return "silver".localized private func getNavigationTitle() -> String { if let qrCodeTypeString = historyItem.qrCodeType, let qrCodeType = QRCodeType(rawValue: qrCodeTypeString) { + // vCard和MCard类型统一使用Contact标题 + if qrCodeType == .vcard || qrCodeType == .mecard { + return "contact".localized + } return qrCodeType.displayName } return "qr_code_detail".localized @@ -793,13 +1339,61 @@ case .silver: return "silver".localized } } .padding() - .background(Color(.systemBackground)) .cornerRadius(12) .shadow(radius: 2) } + // MARK: - 社交应用类型枚举 + private enum SocialAppType { + case instagram + case facebook + case twitter + case whatsapp + case viber + case spotify + } + + // MARK: - 打开社交应用 + private func openSocialApp(content: String, appType: SocialAppType) { + // 直接使用原始链接打开 + if let url = URL(string: content), UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) { success in + if !success { + // 如果无法打开,显示错误提示 + DispatchQueue.main.async { + self.alertMessage = "app_open_failed".localized + self.showingAlert = true + } + } + } + } else { + // 如果URL无效,显示错误提示 + alertMessage = "app_open_failed".localized + showingAlert = true + } + } + + + // MARK: - 跳转到自定义样式界面 private func navigateToCustomStyle() { navigateToStyleView = true } } + +// MARK: - FlowLayout 流布局组件 +struct FlowLayoutView: View { + let spacing: CGFloat + let content: Content + + init(spacing: CGFloat = 20, @ViewBuilder content: () -> Content) { + self.spacing = spacing + self.content = content() + } + + var body: some View { + LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: spacing), count: 4), spacing: spacing) { + content + } + } +} diff --git a/MyQrCode/Views/Settings/SettingsView.swift b/MyQrCode/Views/Settings/SettingsView.swift index 72b95d7..53346be 100644 --- a/MyQrCode/Views/Settings/SettingsView.swift +++ b/MyQrCode/Views/Settings/SettingsView.swift @@ -39,11 +39,6 @@ struct SettingsView: View { .font(.system(size: 36, weight: .light)) .foregroundColor(.blue) } - - Text("settings".localized) - .font(.system(size: 28, weight: .bold, design: .rounded)) - .foregroundColor(.primary) - .id(languageManager.refreshTrigger) } .padding(.top, 20) diff --git a/MyQrCode/de.lproj/InfoPlist.strings b/MyQrCode/de.lproj/InfoPlist.strings index 443bdfb..b59817c 100644 --- a/MyQrCode/de.lproj/InfoPlist.strings +++ b/MyQrCode/de.lproj/InfoPlist.strings @@ -7,3 +7,4 @@ "CFBundleDisplayName" = "MyQrCode"; "CFBundleName" = "MyQrCode"; +"NSContactsUsageDescription" = "Diese App benötigt Zugriff auf Kontakte, um Kontaktinformationen hinzuzufügen"; diff --git a/MyQrCode/de.lproj/Localizable.strings b/MyQrCode/de.lproj/Localizable.strings index cc4016d..7ab8d35 100644 --- a/MyQrCode/de.lproj/Localizable.strings +++ b/MyQrCode/de.lproj/Localizable.strings @@ -760,3 +760,69 @@ // Missing keys (using reference as fallback) "main_title" = "main_title"; "qrcode" = "qrcode"; + + +"add_contact" = "Add Contact"; +"add_to_calendar" = "Add to Calendar"; +"app_open_failed" = "Cannot open app"; +"calendar_description" = "Description"; +"calendar_end_time" = "End Time"; +"calendar_event_add_failed" = "Failed to add event to calendar"; +"calendar_event_added_successfully" = "Event added to calendar successfully"; +"calendar_event_title" = "Event Title"; +"calendar_invalid_start_time" = "Invalid start time format"; +"calendar_location" = "Location"; +"calendar_permission_denied" = "Calendar permission required to add events"; +"calendar_permission_unknown" = "Calendar permission status unknown"; +"calendar_save_failed" = "Failed to save event"; +"calendar_start_time" = "Start Time"; +"call" = "Call"; +"camera" = "Camera"; +"connect_wifi" = "Connect WiFi"; +"contact_add_failed" = "Failed to add contact"; +"contact_added_successfully" = "Contact added to address book successfully"; +"contact_parse_failed" = "Failed to parse contact information"; +"contact_permission_denied" = "Contact permission required to add contact"; +"contact_permission_limited" = "Contact permission limited"; +"contact_permission_unknown" = "Contact permission status unknown"; +"copy_password" = "Copy Password"; +"email_app_failed" = "Cannot open email app"; +"invalid_phone_number" = "Invalid phone number"; +"message" = "Message"; +"open" = "Open"; +"open_facebook" = "Open Facebook"; +"open_instagram" = "Open Instagram"; +"open_spotify" = "Open Spotify"; +"open_viber" = "Open Viber"; +"open_whatsapp" = "Open WhatsApp"; +"open_x" = "Open X"; +"organization" = "Organization"; +"permission_denied_title" = "Permission Denied"; +"phone_call_failed" = "Failed to make phone call"; +"phone_call_initiated" = "Initiating phone call..."; +"phone_call_not_supported" = "Device does not support phone calls"; +"photo" = "Photo"; +"requesting_permission" = "Requesting..."; +"send_email" = "Send Email"; +"send_sms" = "Send SMS"; +"sms_app_failed" = "Failed to open SMS app"; +"sms_app_not_supported" = "Device does not support SMS"; +"sms_app_opened" = "SMS app opened"; +"sms_message" = "Message"; +"sms_phone_number" = "Phone Number"; +"spotify_album" = "Album: %@"; +"spotify_artist" = "Artist: %@"; +"spotify_playlist" = "Playlist: %@"; +"spotify_track" = "Track: %@"; +"title" = "Title"; +"wifi_already_connected" = "Already connected to this WiFi network"; +"wifi_connected_successfully" = "WiFi connected successfully!"; +"wifi_connecting" = "Attempting to connect to WiFi network..."; +"wifi_connection_failed" = "WiFi connection failed"; +"wifi_invalid_password" = "Invalid WiFi password format"; +"wifi_invalid_ssid" = "Invalid WiFi network name"; +"wifi_manual_setup_instruction" = "Please set up WiFi manually:\nNetwork Name: %@\nPassword: %@\n\nGo to: Settings > WiFi > Select Network > Enter Password"; +"wifi_opened_settings" = "Opened system WiFi settings"; +"wifi_password_copied" = "WiFi password copied to clipboard"; +"wifi_setup_instruction" = "Please go to Settings > WiFi to connect manually"; +"wifi_user_denied" = "User denied WiFi connection request"; \ No newline at end of file diff --git a/MyQrCode/en.lproj/InfoPlist.strings b/MyQrCode/en.lproj/InfoPlist.strings index 443bdfb..c0d9d85 100644 --- a/MyQrCode/en.lproj/InfoPlist.strings +++ b/MyQrCode/en.lproj/InfoPlist.strings @@ -7,3 +7,6 @@ "CFBundleDisplayName" = "MyQrCode"; "CFBundleName" = "MyQrCode"; +"NSContactsUsageDescription" = "This app needs access to contacts to add contact information"; +"NSCalendarsUsageDescription" = "This app needs access to calendar to add events"; +"NSCalendarsWriteOnlyAccessUsageDescription" = "This app needs write-only access to add calendar events"; diff --git a/MyQrCode/en.lproj/Localizable.strings b/MyQrCode/en.lproj/Localizable.strings index 7e7988f..6e49e13 100644 --- a/MyQrCode/en.lproj/Localizable.strings +++ b/MyQrCode/en.lproj/Localizable.strings @@ -55,6 +55,7 @@ "parsed_info" = "Parsed Information"; "original_content" = "Original Content"; "copy_content" = "Copy Content"; +"open" = "Open"; "open_link" = "Open Link"; "decorate_code" = "Decorate Code"; "qr_code_has_style" = "This QR code has custom style, tap to edit"; @@ -803,6 +804,70 @@ "wifi_invalid_ssid" = "Invalid WiFi network name"; "wifi_invalid_password" = "Invalid WiFi password format"; +// Contact related +"contact_added_successfully" = "Contact added to address book successfully"; +"contact_add_failed" = "Failed to add contact"; +"contact_permission_denied" = "Contact permission required to add contact"; +"contact_permission_limited" = "Contact permission limited"; +"contact_permission_unknown" = "Contact permission status unknown"; +"contact_parse_failed" = "Failed to parse contact information"; +"phone_call_initiated" = "Initiating phone call..."; +"phone_call_failed" = "Failed to make phone call"; +"phone_call_not_supported" = "Device does not support phone calls"; +"invalid_phone_number" = "Invalid phone number"; +"sms_app_opened" = "SMS app opened"; +"sms_app_failed" = "Failed to open SMS app"; +"sms_app_not_supported" = "Device does not support SMS"; +"contact" = "Contact"; + +// Button Labels +"add_contact" = "Add Contact"; +"call" = "Call"; +"message" = "Message"; +"send_sms" = "Send SMS"; +"add_to_calendar" = "Add to Calendar"; +"calendar_event_added_successfully" = "Event added to calendar successfully"; +"calendar_event_add_failed" = "Failed to add event to calendar"; +"calendar_permission_denied" = "Calendar permission required to add events"; +"calendar_permission_unknown" = "Calendar permission status unknown"; +"calendar_invalid_start_time" = "Invalid start time format"; +"calendar_save_failed" = "Failed to save event"; +"send_email" = "Send Email"; +"email_app_failed" = "Cannot open email app"; +"email_subject" = "Subject"; +"email_body" = "Body"; +"calendar_event_title" = "Event Title"; +"calendar_start_time" = "Start Time"; +"calendar_end_time" = "End Time"; +"calendar_location" = "Location"; +"calendar_description" = "Description"; +"open_instagram" = "Open Instagram"; +"open_facebook" = "Open Facebook"; +"open_x" = "Open X"; +"open_whatsapp" = "Open WhatsApp"; +"open_viber" = "Open Viber"; +"open_spotify" = "Open Spotify"; +"app_open_failed" = "Cannot open app"; +"spotify_track" = "Track: %@"; +"spotify_album" = "Album: %@"; +"spotify_artist" = "Artist: %@"; +"spotify_playlist" = "Playlist: %@"; +"sms_phone_number" = "Phone Number"; +"sms_message" = "Message"; +"copy_password" = "Copy Password"; +"connect_wifi" = "Connect WiFi"; +"open_link" = "Open Link"; +"favorite" = "Favorite"; +"copy" = "Copy"; + +// Contact Info Fields +"name" = "Name"; +"phone" = "Phone"; +"email" = "Email"; +"organization" = "Organization"; +"title" = "Title"; +"address" = "Address"; + // Picker View "picker_view_cancel" = "Cancel Selection"; "picker_view_done" = "Done Selection"; diff --git a/MyQrCode/es.lproj/InfoPlist.strings b/MyQrCode/es.lproj/InfoPlist.strings index 443bdfb..87ae148 100644 --- a/MyQrCode/es.lproj/InfoPlist.strings +++ b/MyQrCode/es.lproj/InfoPlist.strings @@ -7,3 +7,4 @@ "CFBundleDisplayName" = "MyQrCode"; "CFBundleName" = "MyQrCode"; +"NSContactsUsageDescription" = "Esta aplicación necesita acceso a los contactos para agregar información de contacto"; diff --git a/MyQrCode/es.lproj/Localizable.strings b/MyQrCode/es.lproj/Localizable.strings index 82d89bb..bc5dc4d 100644 --- a/MyQrCode/es.lproj/Localizable.strings +++ b/MyQrCode/es.lproj/Localizable.strings @@ -760,3 +760,69 @@ // Missing keys (using reference as fallback) "main_title" = "main_title"; "qrcode" = "qrcode"; + + +"add_contact" = "Add Contact"; +"add_to_calendar" = "Add to Calendar"; +"app_open_failed" = "Cannot open app"; +"calendar_description" = "Description"; +"calendar_end_time" = "End Time"; +"calendar_event_add_failed" = "Failed to add event to calendar"; +"calendar_event_added_successfully" = "Event added to calendar successfully"; +"calendar_event_title" = "Event Title"; +"calendar_invalid_start_time" = "Invalid start time format"; +"calendar_location" = "Location"; +"calendar_permission_denied" = "Calendar permission required to add events"; +"calendar_permission_unknown" = "Calendar permission status unknown"; +"calendar_save_failed" = "Failed to save event"; +"calendar_start_time" = "Start Time"; +"call" = "Call"; +"camera" = "Camera"; +"connect_wifi" = "Connect WiFi"; +"contact_add_failed" = "Failed to add contact"; +"contact_added_successfully" = "Contact added to address book successfully"; +"contact_parse_failed" = "Failed to parse contact information"; +"contact_permission_denied" = "Contact permission required to add contact"; +"contact_permission_limited" = "Contact permission limited"; +"contact_permission_unknown" = "Contact permission status unknown"; +"copy_password" = "Copy Password"; +"email_app_failed" = "Cannot open email app"; +"invalid_phone_number" = "Invalid phone number"; +"message" = "Message"; +"open" = "Open"; +"open_facebook" = "Open Facebook"; +"open_instagram" = "Open Instagram"; +"open_spotify" = "Open Spotify"; +"open_viber" = "Open Viber"; +"open_whatsapp" = "Open WhatsApp"; +"open_x" = "Open X"; +"organization" = "Organization"; +"permission_denied_title" = "Permission Denied"; +"phone_call_failed" = "Failed to make phone call"; +"phone_call_initiated" = "Initiating phone call..."; +"phone_call_not_supported" = "Device does not support phone calls"; +"photo" = "Photo"; +"requesting_permission" = "Requesting..."; +"send_email" = "Send Email"; +"send_sms" = "Send SMS"; +"sms_app_failed" = "Failed to open SMS app"; +"sms_app_not_supported" = "Device does not support SMS"; +"sms_app_opened" = "SMS app opened"; +"sms_message" = "Message"; +"sms_phone_number" = "Phone Number"; +"spotify_album" = "Album: %@"; +"spotify_artist" = "Artist: %@"; +"spotify_playlist" = "Playlist: %@"; +"spotify_track" = "Track: %@"; +"title" = "Title"; +"wifi_already_connected" = "Already connected to this WiFi network"; +"wifi_connected_successfully" = "WiFi connected successfully!"; +"wifi_connecting" = "Attempting to connect to WiFi network..."; +"wifi_connection_failed" = "WiFi connection failed"; +"wifi_invalid_password" = "Invalid WiFi password format"; +"wifi_invalid_ssid" = "Invalid WiFi network name"; +"wifi_manual_setup_instruction" = "Please set up WiFi manually:\nNetwork Name: %@\nPassword: %@\n\nGo to: Settings > WiFi > Select Network > Enter Password"; +"wifi_opened_settings" = "Opened system WiFi settings"; +"wifi_password_copied" = "WiFi password copied to clipboard"; +"wifi_setup_instruction" = "Please go to Settings > WiFi to connect manually"; +"wifi_user_denied" = "User denied WiFi connection request"; \ No newline at end of file diff --git a/MyQrCode/fr.lproj/InfoPlist.strings b/MyQrCode/fr.lproj/InfoPlist.strings index 443bdfb..fc3f76d 100644 --- a/MyQrCode/fr.lproj/InfoPlist.strings +++ b/MyQrCode/fr.lproj/InfoPlist.strings @@ -7,3 +7,4 @@ "CFBundleDisplayName" = "MyQrCode"; "CFBundleName" = "MyQrCode"; +"NSContactsUsageDescription" = "Cette application a besoin d'accéder aux contacts pour ajouter des informations de contact"; diff --git a/MyQrCode/fr.lproj/Localizable.strings b/MyQrCode/fr.lproj/Localizable.strings index 1a63fd9..2080d19 100644 --- a/MyQrCode/fr.lproj/Localizable.strings +++ b/MyQrCode/fr.lproj/Localizable.strings @@ -760,3 +760,69 @@ // Missing keys (using reference as fallback) "main_title" = "main_title"; "qrcode" = "qrcode"; + + +"add_contact" = "Add Contact"; +"add_to_calendar" = "Add to Calendar"; +"app_open_failed" = "Cannot open app"; +"calendar_description" = "Description"; +"calendar_end_time" = "End Time"; +"calendar_event_add_failed" = "Failed to add event to calendar"; +"calendar_event_added_successfully" = "Event added to calendar successfully"; +"calendar_event_title" = "Event Title"; +"calendar_invalid_start_time" = "Invalid start time format"; +"calendar_location" = "Location"; +"calendar_permission_denied" = "Calendar permission required to add events"; +"calendar_permission_unknown" = "Calendar permission status unknown"; +"calendar_save_failed" = "Failed to save event"; +"calendar_start_time" = "Start Time"; +"call" = "Call"; +"camera" = "Camera"; +"connect_wifi" = "Connect WiFi"; +"contact_add_failed" = "Failed to add contact"; +"contact_added_successfully" = "Contact added to address book successfully"; +"contact_parse_failed" = "Failed to parse contact information"; +"contact_permission_denied" = "Contact permission required to add contact"; +"contact_permission_limited" = "Contact permission limited"; +"contact_permission_unknown" = "Contact permission status unknown"; +"copy_password" = "Copy Password"; +"email_app_failed" = "Cannot open email app"; +"invalid_phone_number" = "Invalid phone number"; +"message" = "Message"; +"open" = "Open"; +"open_facebook" = "Open Facebook"; +"open_instagram" = "Open Instagram"; +"open_spotify" = "Open Spotify"; +"open_viber" = "Open Viber"; +"open_whatsapp" = "Open WhatsApp"; +"open_x" = "Open X"; +"organization" = "Organization"; +"permission_denied_title" = "Permission Denied"; +"phone_call_failed" = "Failed to make phone call"; +"phone_call_initiated" = "Initiating phone call..."; +"phone_call_not_supported" = "Device does not support phone calls"; +"photo" = "Photo"; +"requesting_permission" = "Requesting..."; +"send_email" = "Send Email"; +"send_sms" = "Send SMS"; +"sms_app_failed" = "Failed to open SMS app"; +"sms_app_not_supported" = "Device does not support SMS"; +"sms_app_opened" = "SMS app opened"; +"sms_message" = "Message"; +"sms_phone_number" = "Phone Number"; +"spotify_album" = "Album: %@"; +"spotify_artist" = "Artist: %@"; +"spotify_playlist" = "Playlist: %@"; +"spotify_track" = "Track: %@"; +"title" = "Title"; +"wifi_already_connected" = "Already connected to this WiFi network"; +"wifi_connected_successfully" = "WiFi connected successfully!"; +"wifi_connecting" = "Attempting to connect to WiFi network..."; +"wifi_connection_failed" = "WiFi connection failed"; +"wifi_invalid_password" = "Invalid WiFi password format"; +"wifi_invalid_ssid" = "Invalid WiFi network name"; +"wifi_manual_setup_instruction" = "Please set up WiFi manually:\nNetwork Name: %@\nPassword: %@\n\nGo to: Settings > WiFi > Select Network > Enter Password"; +"wifi_opened_settings" = "Opened system WiFi settings"; +"wifi_password_copied" = "WiFi password copied to clipboard"; +"wifi_setup_instruction" = "Please go to Settings > WiFi to connect manually"; +"wifi_user_denied" = "User denied WiFi connection request"; \ No newline at end of file diff --git a/MyQrCode/it.lproj/InfoPlist.strings b/MyQrCode/it.lproj/InfoPlist.strings index 443bdfb..33bbd67 100644 --- a/MyQrCode/it.lproj/InfoPlist.strings +++ b/MyQrCode/it.lproj/InfoPlist.strings @@ -7,3 +7,4 @@ "CFBundleDisplayName" = "MyQrCode"; "CFBundleName" = "MyQrCode"; +"NSContactsUsageDescription" = "Questa app ha bisogno di accedere ai contatti per aggiungere informazioni di contatto"; diff --git a/MyQrCode/it.lproj/Localizable.strings b/MyQrCode/it.lproj/Localizable.strings index d875e52..7591180 100644 --- a/MyQrCode/it.lproj/Localizable.strings +++ b/MyQrCode/it.lproj/Localizable.strings @@ -760,3 +760,69 @@ // Missing keys (using reference as fallback) "main_title" = "main_title"; "qrcode" = "qrcode"; + + +"add_contact" = "Add Contact"; +"add_to_calendar" = "Add to Calendar"; +"app_open_failed" = "Cannot open app"; +"calendar_description" = "Description"; +"calendar_end_time" = "End Time"; +"calendar_event_add_failed" = "Failed to add event to calendar"; +"calendar_event_added_successfully" = "Event added to calendar successfully"; +"calendar_event_title" = "Event Title"; +"calendar_invalid_start_time" = "Invalid start time format"; +"calendar_location" = "Location"; +"calendar_permission_denied" = "Calendar permission required to add events"; +"calendar_permission_unknown" = "Calendar permission status unknown"; +"calendar_save_failed" = "Failed to save event"; +"calendar_start_time" = "Start Time"; +"call" = "Call"; +"camera" = "Camera"; +"connect_wifi" = "Connect WiFi"; +"contact_add_failed" = "Failed to add contact"; +"contact_added_successfully" = "Contact added to address book successfully"; +"contact_parse_failed" = "Failed to parse contact information"; +"contact_permission_denied" = "Contact permission required to add contact"; +"contact_permission_limited" = "Contact permission limited"; +"contact_permission_unknown" = "Contact permission status unknown"; +"copy_password" = "Copy Password"; +"email_app_failed" = "Cannot open email app"; +"invalid_phone_number" = "Invalid phone number"; +"message" = "Message"; +"open" = "Open"; +"open_facebook" = "Open Facebook"; +"open_instagram" = "Open Instagram"; +"open_spotify" = "Open Spotify"; +"open_viber" = "Open Viber"; +"open_whatsapp" = "Open WhatsApp"; +"open_x" = "Open X"; +"organization" = "Organization"; +"permission_denied_title" = "Permission Denied"; +"phone_call_failed" = "Failed to make phone call"; +"phone_call_initiated" = "Initiating phone call..."; +"phone_call_not_supported" = "Device does not support phone calls"; +"photo" = "Photo"; +"requesting_permission" = "Requesting..."; +"send_email" = "Send Email"; +"send_sms" = "Send SMS"; +"sms_app_failed" = "Failed to open SMS app"; +"sms_app_not_supported" = "Device does not support SMS"; +"sms_app_opened" = "SMS app opened"; +"sms_message" = "Message"; +"sms_phone_number" = "Phone Number"; +"spotify_album" = "Album: %@"; +"spotify_artist" = "Artist: %@"; +"spotify_playlist" = "Playlist: %@"; +"spotify_track" = "Track: %@"; +"title" = "Title"; +"wifi_already_connected" = "Already connected to this WiFi network"; +"wifi_connected_successfully" = "WiFi connected successfully!"; +"wifi_connecting" = "Attempting to connect to WiFi network..."; +"wifi_connection_failed" = "WiFi connection failed"; +"wifi_invalid_password" = "Invalid WiFi password format"; +"wifi_invalid_ssid" = "Invalid WiFi network name"; +"wifi_manual_setup_instruction" = "Please set up WiFi manually:\nNetwork Name: %@\nPassword: %@\n\nGo to: Settings > WiFi > Select Network > Enter Password"; +"wifi_opened_settings" = "Opened system WiFi settings"; +"wifi_password_copied" = "WiFi password copied to clipboard"; +"wifi_setup_instruction" = "Please go to Settings > WiFi to connect manually"; +"wifi_user_denied" = "User denied WiFi connection request"; \ No newline at end of file diff --git a/MyQrCode/ja.lproj/InfoPlist.strings b/MyQrCode/ja.lproj/InfoPlist.strings index 443bdfb..2bd1f4d 100644 --- a/MyQrCode/ja.lproj/InfoPlist.strings +++ b/MyQrCode/ja.lproj/InfoPlist.strings @@ -7,3 +7,4 @@ "CFBundleDisplayName" = "MyQrCode"; "CFBundleName" = "MyQrCode"; +"NSContactsUsageDescription" = "このアプリは連絡先情報を追加するために連絡先へのアクセスが必要です"; diff --git a/MyQrCode/ja.lproj/Localizable.strings b/MyQrCode/ja.lproj/Localizable.strings index 265768d..78f35cf 100644 --- a/MyQrCode/ja.lproj/Localizable.strings +++ b/MyQrCode/ja.lproj/Localizable.strings @@ -760,3 +760,69 @@ // Missing keys (using reference as fallback) "main_title" = "main_title"; "qrcode" = "qrcode"; + + +"add_contact" = "Add Contact"; +"add_to_calendar" = "Add to Calendar"; +"app_open_failed" = "Cannot open app"; +"calendar_description" = "Description"; +"calendar_end_time" = "End Time"; +"calendar_event_add_failed" = "Failed to add event to calendar"; +"calendar_event_added_successfully" = "Event added to calendar successfully"; +"calendar_event_title" = "Event Title"; +"calendar_invalid_start_time" = "Invalid start time format"; +"calendar_location" = "Location"; +"calendar_permission_denied" = "Calendar permission required to add events"; +"calendar_permission_unknown" = "Calendar permission status unknown"; +"calendar_save_failed" = "Failed to save event"; +"calendar_start_time" = "Start Time"; +"call" = "Call"; +"camera" = "Camera"; +"connect_wifi" = "Connect WiFi"; +"contact_add_failed" = "Failed to add contact"; +"contact_added_successfully" = "Contact added to address book successfully"; +"contact_parse_failed" = "Failed to parse contact information"; +"contact_permission_denied" = "Contact permission required to add contact"; +"contact_permission_limited" = "Contact permission limited"; +"contact_permission_unknown" = "Contact permission status unknown"; +"copy_password" = "Copy Password"; +"email_app_failed" = "Cannot open email app"; +"invalid_phone_number" = "Invalid phone number"; +"message" = "Message"; +"open" = "Open"; +"open_facebook" = "Open Facebook"; +"open_instagram" = "Open Instagram"; +"open_spotify" = "Open Spotify"; +"open_viber" = "Open Viber"; +"open_whatsapp" = "Open WhatsApp"; +"open_x" = "Open X"; +"organization" = "Organization"; +"permission_denied_title" = "Permission Denied"; +"phone_call_failed" = "Failed to make phone call"; +"phone_call_initiated" = "Initiating phone call..."; +"phone_call_not_supported" = "Device does not support phone calls"; +"photo" = "Photo"; +"requesting_permission" = "Requesting..."; +"send_email" = "Send Email"; +"send_sms" = "Send SMS"; +"sms_app_failed" = "Failed to open SMS app"; +"sms_app_not_supported" = "Device does not support SMS"; +"sms_app_opened" = "SMS app opened"; +"sms_message" = "Message"; +"sms_phone_number" = "Phone Number"; +"spotify_album" = "Album: %@"; +"spotify_artist" = "Artist: %@"; +"spotify_playlist" = "Playlist: %@"; +"spotify_track" = "Track: %@"; +"title" = "Title"; +"wifi_already_connected" = "Already connected to this WiFi network"; +"wifi_connected_successfully" = "WiFi connected successfully!"; +"wifi_connecting" = "Attempting to connect to WiFi network..."; +"wifi_connection_failed" = "WiFi connection failed"; +"wifi_invalid_password" = "Invalid WiFi password format"; +"wifi_invalid_ssid" = "Invalid WiFi network name"; +"wifi_manual_setup_instruction" = "Please set up WiFi manually:\nNetwork Name: %@\nPassword: %@\n\nGo to: Settings > WiFi > Select Network > Enter Password"; +"wifi_opened_settings" = "Opened system WiFi settings"; +"wifi_password_copied" = "WiFi password copied to clipboard"; +"wifi_setup_instruction" = "Please go to Settings > WiFi to connect manually"; +"wifi_user_denied" = "User denied WiFi connection request"; \ No newline at end of file diff --git a/MyQrCode/ko.lproj/InfoPlist.strings b/MyQrCode/ko.lproj/InfoPlist.strings index 443bdfb..0f7d9c3 100644 --- a/MyQrCode/ko.lproj/InfoPlist.strings +++ b/MyQrCode/ko.lproj/InfoPlist.strings @@ -7,3 +7,4 @@ "CFBundleDisplayName" = "MyQrCode"; "CFBundleName" = "MyQrCode"; +"NSContactsUsageDescription" = "이 앱은 연락처 정보를 추가하기 위해 연락처에 대한 액세스가 필요합니다"; diff --git a/MyQrCode/ko.lproj/Localizable.strings b/MyQrCode/ko.lproj/Localizable.strings index 6c743ce..6237a08 100644 --- a/MyQrCode/ko.lproj/Localizable.strings +++ b/MyQrCode/ko.lproj/Localizable.strings @@ -760,3 +760,69 @@ // Missing keys (using reference as fallback) "main_title" = "main_title"; "qrcode" = "qrcode"; + + +"add_contact" = "Add Contact"; +"add_to_calendar" = "Add to Calendar"; +"app_open_failed" = "Cannot open app"; +"calendar_description" = "Description"; +"calendar_end_time" = "End Time"; +"calendar_event_add_failed" = "Failed to add event to calendar"; +"calendar_event_added_successfully" = "Event added to calendar successfully"; +"calendar_event_title" = "Event Title"; +"calendar_invalid_start_time" = "Invalid start time format"; +"calendar_location" = "Location"; +"calendar_permission_denied" = "Calendar permission required to add events"; +"calendar_permission_unknown" = "Calendar permission status unknown"; +"calendar_save_failed" = "Failed to save event"; +"calendar_start_time" = "Start Time"; +"call" = "Call"; +"camera" = "Camera"; +"connect_wifi" = "Connect WiFi"; +"contact_add_failed" = "Failed to add contact"; +"contact_added_successfully" = "Contact added to address book successfully"; +"contact_parse_failed" = "Failed to parse contact information"; +"contact_permission_denied" = "Contact permission required to add contact"; +"contact_permission_limited" = "Contact permission limited"; +"contact_permission_unknown" = "Contact permission status unknown"; +"copy_password" = "Copy Password"; +"email_app_failed" = "Cannot open email app"; +"invalid_phone_number" = "Invalid phone number"; +"message" = "Message"; +"open" = "Open"; +"open_facebook" = "Open Facebook"; +"open_instagram" = "Open Instagram"; +"open_spotify" = "Open Spotify"; +"open_viber" = "Open Viber"; +"open_whatsapp" = "Open WhatsApp"; +"open_x" = "Open X"; +"organization" = "Organization"; +"permission_denied_title" = "Permission Denied"; +"phone_call_failed" = "Failed to make phone call"; +"phone_call_initiated" = "Initiating phone call..."; +"phone_call_not_supported" = "Device does not support phone calls"; +"photo" = "Photo"; +"requesting_permission" = "Requesting..."; +"send_email" = "Send Email"; +"send_sms" = "Send SMS"; +"sms_app_failed" = "Failed to open SMS app"; +"sms_app_not_supported" = "Device does not support SMS"; +"sms_app_opened" = "SMS app opened"; +"sms_message" = "Message"; +"sms_phone_number" = "Phone Number"; +"spotify_album" = "Album: %@"; +"spotify_artist" = "Artist: %@"; +"spotify_playlist" = "Playlist: %@"; +"spotify_track" = "Track: %@"; +"title" = "Title"; +"wifi_already_connected" = "Already connected to this WiFi network"; +"wifi_connected_successfully" = "WiFi connected successfully!"; +"wifi_connecting" = "Attempting to connect to WiFi network..."; +"wifi_connection_failed" = "WiFi connection failed"; +"wifi_invalid_password" = "Invalid WiFi password format"; +"wifi_invalid_ssid" = "Invalid WiFi network name"; +"wifi_manual_setup_instruction" = "Please set up WiFi manually:\nNetwork Name: %@\nPassword: %@\n\nGo to: Settings > WiFi > Select Network > Enter Password"; +"wifi_opened_settings" = "Opened system WiFi settings"; +"wifi_password_copied" = "WiFi password copied to clipboard"; +"wifi_setup_instruction" = "Please go to Settings > WiFi to connect manually"; +"wifi_user_denied" = "User denied WiFi connection request"; \ No newline at end of file diff --git a/MyQrCode/pt.lproj/InfoPlist.strings b/MyQrCode/pt.lproj/InfoPlist.strings index 443bdfb..4fbc6df 100644 --- a/MyQrCode/pt.lproj/InfoPlist.strings +++ b/MyQrCode/pt.lproj/InfoPlist.strings @@ -7,3 +7,4 @@ "CFBundleDisplayName" = "MyQrCode"; "CFBundleName" = "MyQrCode"; +"NSContactsUsageDescription" = "Este aplicativo precisa acessar os contatos para adicionar informações de contato"; diff --git a/MyQrCode/pt.lproj/Localizable.strings b/MyQrCode/pt.lproj/Localizable.strings index 67a7158..6c3921d 100644 --- a/MyQrCode/pt.lproj/Localizable.strings +++ b/MyQrCode/pt.lproj/Localizable.strings @@ -760,3 +760,69 @@ // Missing keys (using reference as fallback) "main_title" = "main_title"; "qrcode" = "qrcode"; + + +"add_contact" = "Add Contact"; +"add_to_calendar" = "Add to Calendar"; +"app_open_failed" = "Cannot open app"; +"calendar_description" = "Description"; +"calendar_end_time" = "End Time"; +"calendar_event_add_failed" = "Failed to add event to calendar"; +"calendar_event_added_successfully" = "Event added to calendar successfully"; +"calendar_event_title" = "Event Title"; +"calendar_invalid_start_time" = "Invalid start time format"; +"calendar_location" = "Location"; +"calendar_permission_denied" = "Calendar permission required to add events"; +"calendar_permission_unknown" = "Calendar permission status unknown"; +"calendar_save_failed" = "Failed to save event"; +"calendar_start_time" = "Start Time"; +"call" = "Call"; +"camera" = "Camera"; +"connect_wifi" = "Connect WiFi"; +"contact_add_failed" = "Failed to add contact"; +"contact_added_successfully" = "Contact added to address book successfully"; +"contact_parse_failed" = "Failed to parse contact information"; +"contact_permission_denied" = "Contact permission required to add contact"; +"contact_permission_limited" = "Contact permission limited"; +"contact_permission_unknown" = "Contact permission status unknown"; +"copy_password" = "Copy Password"; +"email_app_failed" = "Cannot open email app"; +"invalid_phone_number" = "Invalid phone number"; +"message" = "Message"; +"open" = "Open"; +"open_facebook" = "Open Facebook"; +"open_instagram" = "Open Instagram"; +"open_spotify" = "Open Spotify"; +"open_viber" = "Open Viber"; +"open_whatsapp" = "Open WhatsApp"; +"open_x" = "Open X"; +"organization" = "Organization"; +"permission_denied_title" = "Permission Denied"; +"phone_call_failed" = "Failed to make phone call"; +"phone_call_initiated" = "Initiating phone call..."; +"phone_call_not_supported" = "Device does not support phone calls"; +"photo" = "Photo"; +"requesting_permission" = "Requesting..."; +"send_email" = "Send Email"; +"send_sms" = "Send SMS"; +"sms_app_failed" = "Failed to open SMS app"; +"sms_app_not_supported" = "Device does not support SMS"; +"sms_app_opened" = "SMS app opened"; +"sms_message" = "Message"; +"sms_phone_number" = "Phone Number"; +"spotify_album" = "Album: %@"; +"spotify_artist" = "Artist: %@"; +"spotify_playlist" = "Playlist: %@"; +"spotify_track" = "Track: %@"; +"title" = "Title"; +"wifi_already_connected" = "Already connected to this WiFi network"; +"wifi_connected_successfully" = "WiFi connected successfully!"; +"wifi_connecting" = "Attempting to connect to WiFi network..."; +"wifi_connection_failed" = "WiFi connection failed"; +"wifi_invalid_password" = "Invalid WiFi password format"; +"wifi_invalid_ssid" = "Invalid WiFi network name"; +"wifi_manual_setup_instruction" = "Please set up WiFi manually:\nNetwork Name: %@\nPassword: %@\n\nGo to: Settings > WiFi > Select Network > Enter Password"; +"wifi_opened_settings" = "Opened system WiFi settings"; +"wifi_password_copied" = "WiFi password copied to clipboard"; +"wifi_setup_instruction" = "Please go to Settings > WiFi to connect manually"; +"wifi_user_denied" = "User denied WiFi connection request"; \ No newline at end of file diff --git a/MyQrCode/pt.lproj/Localizable.strings.backup b/MyQrCode/pt.lproj/Localizable.strings.backup new file mode 100644 index 0000000..67a7158 --- /dev/null +++ b/MyQrCode/pt.lproj/Localizable.strings.backup @@ -0,0 +1,762 @@ +// App +"app_name" = "MyQrCode"; +"settings" = "Configurações"; +"scan" = "Escanear"; +"create" = "Criar"; +"history" = "Histórico"; +"about" = "Sobre"; + +// Language Manager +"chinese_language" = "Chinês"; +"system_language" = "Sistema"; + +// Input Component Factory +"input_any_text_content" = "Digite qualquer conteúdo de texto..."; +"input_url_content" = "Digite a URL..."; +"input_email_content" = "Digite o endereço de e-mail..."; +"input_phone_content" = "Digite o número de telefone..."; +"input_sms_content" = "Digite a mensagem SMS..."; +"input_wifi_content" = "Digite as informações WiFi..."; +"input_contact_content" = "Digite as informações de contato..."; +"input_calendar_content" = "Digite o evento do calendário..."; +"input_location_content" = "Digite as informações de localização..."; +"input_social_content" = "Digite as informações de redes sociais..."; + +// Input Field View +"input_field_title" = "Título"; +"input_field_content" = "Conteúdo"; +"input_field_placeholder" = "Marcador de posição"; + +// Input Hint View +"input_hint_title" = "Dica"; +"input_hint_content" = "Conteúdo da dica"; + +// Input Title View +"input_title_text" = "Texto do título"; + +// Keyboard Toolbar View +"keyboard_toolbar_done" = "Concluído"; +"keyboard_toolbar_cancel" = "Cancelar"; + +// List View +"list_view_title" = "Visualização da lista"; +"list_view_empty" = "A lista está vazia"; + +// Picker View +"picker_view_title" = "Seleção"; +"picker_view_cancel" = "Cancelar"; +"picker_view_done" = "Concluído"; + +// QRCode Preview View +"qrcode_preview_title" = "Visualização do código QR"; +"qrcode_preview_save" = "Salvar"; +"qrcode_preview_share" = "Compartilhar"; + +// Utility Functions +"utility_success" = "Sucesso"; +"utility_error" = "Erro"; +"utility_warning" = "Aviso"; +"utility_info" = "Informação"; + +// Barcode Detail View +"barcode_detail_title" = "Detalhes do código de barras"; +"barcode_detail_type" = "Tipo"; +"barcode_detail_content" = "Conteúdo"; +"barcode_detail_copy" = "Copiar"; +"barcode_detail_share" = "Compartilhar"; + +// Barcode Preview View +"barcode_preview_title" = "Visualização do código de barras"; +"barcode_preview_save" = "Salvar"; +"barcode_preview_share" = "Compartilhar"; + +// Code Type Selection View +"code_type_selection_title" = "Seleção do tipo de código"; +"code_type_text" = "Texto"; +"code_type_url" = "URL"; +"code_type_email" = "E-mail"; +"code_type_phone" = "Telefone"; +"code_type_sms" = "SMS"; +"code_type_wifi" = "WiFi"; +"code_type_contact" = "Contato"; +"code_type_calendar" = "Calendário"; +"code_type_location" = "Localização"; +"code_type_social" = "Redes sociais"; + +// Create QR Code View +"create_qrcode_title" = "Criar código QR"; +"create_qrcode_type" = "Tipo"; +"create_qrcode_content" = "Conteúdo"; +"create_qrcode_generate" = "Gerar"; + +// Create Code View +"create_code_title" = "Criar código"; +"create_code_type" = "Tipo"; +"create_code_content" = "Conteúdo"; +"create_code_generate" = "Gerar"; + +// History View +"history_title" = "Histórico"; +"history_empty" = "Nenhum histórico"; +"history_delete" = "Excluir"; +"history_share" = "Compartilhar"; +"history_copy" = "Copiar"; +"history_favorite" = "Favorito"; +"history_unfavorite" = "Remover dos favoritos"; + +// QR Code Detail View +"qrcode_detail_title" = "Detalhes do código QR"; +"qrcode_detail_type" = "Tipo"; +"qrcode_detail_content" = "Conteúdo"; +"qrcode_detail_copy" = "Copiar"; +"qrcode_detail_share" = "Compartilhar"; + +// Settings View +"select_app_language" = "Selecionar idioma do aplicativo"; +"language" = "Idioma"; +"app_info" = "Informações do aplicativo"; +"version" = "Versão"; +"version_number" = "1.0.0"; +"build_version" = "Build"; +"build_number" = "1"; +"app_permissions" = "Permissões do aplicativo"; +"manage_app_permissions" = "Gerenciar permissões de câmera e biblioteca de fotos para este aplicativo."; +"privacy_policy" = "Política de privacidade"; +"view_privacy_policy" = "Consulte nossa política de privacidade e práticas de processamento de dados."; + +// App Permissions View +"camera_permission" = "Permissão da câmera"; +"photo_library_permission" = "Permissão da biblioteca de fotos"; +"permission_authorized" = "Autorizado"; +"permission_denied" = "Negado"; +"permission_restricted" = "Restrito"; +"permission_not_determined" = "Não determinado"; +"permission_limited" = "Limitado"; +"request_permission" = "Solicitar permissão"; +"open_settings" = "Abrir configurações"; +"permission_granted" = "Permissão concedida"; + +// Navigation +"back" = "Voltar"; +"next" = "Próximo"; +"cancel" = "Cancelar"; +"save" = "Salvar"; +"delete" = "Excluir"; +"edit" = "Editar"; +"done" = "Concluído"; + +// Common +"ok" = "OK"; +"yes" = "Sim"; +"no" = "Não"; +"loading" = "Carregando..."; +"error" = "Erro"; +"success" = "Sucesso"; +"warning" = "Aviso"; +"info" = "Informação"; + +// Pagination +"load_more" = "Carregar mais"; +"no_more_data" = "Não há mais dados"; +"loading_more" = "Carregando mais..."; + +// History +"no_history_records" = "Nenhum registro de histórico"; + +// Missing keys (using English as fallback) +"abstract" = "Abstract"; +"actions" = "Actions"; +"add_content" = "Add Content"; +"add_to_image" = "Add to Image"; +"add_to_picture" = "Add to Picture"; +"add_to_picture_button" = "Add to Picture Button"; +"add_to_picture_title" = "Add to Picture"; +"added_to_favorites" = "Added to favorites"; +"address" = "Address"; +"all" = "All"; +"all_ascii" = "All ASCII"; +"allowed_characters" = "Allowed characters: %@"; +"app_description" = "Scan QR codes and barcodes with ease"; +"app_description_long" = "QR Scanner is a powerful QR code and barcode scanning app that supports multiple barcode format recognition and creation."; +"app_title" = "MyQrCode"; +"arc" = "Arc"; +"architecture_mismatch_detected" = "🔄 Architecture mismatch detected, deleting existing database file"; +"arrow" = "Arrow"; +"artist" = "Artist"; +"authorized" = "Authorized"; +"auto_result_1s" = "Result will be shown in 1 second"; +"aztec_characters" = "All ASCII characters (0-127)"; +"aztec_only_contains" = "Aztec can only contain ASCII characters"; +"background_color" = "Background Color"; +"barcode" = "Barcode"; +"barcode_content" = "Barcode Content"; +"barcode_detail" = "Barcode Detail"; +"barcode_format_incorrect" = "Barcode format incorrect"; +"barcode_type" = "Barcode Type"; +"bars_horizontal" = "Horizontal Bars"; +"bars_vertical" = "Vertical Bars"; +"basic_info" = "Basic Information"; +"bcc_address" = "BCC Address"; +"bcc_email_placeholder" = "bcc@example.com"; +"birthday" = "Birthday"; +"birthday_format" = "%@-%@-%@"; +"black" = "Black"; +"blob" = "Blob"; +"blue" = "Blue"; +"brown" = "Brown"; +"calendar" = "Calendar"; +"calendar_content_format" = "Event: %@"; +"calendar_event" = "Calendar Event"; +"calendar_event_description" = "\nDescription: %@"; +"calendar_event_info" = "Event: %@\nStart: %@\nEnd: %@"; +"calendar_event_location" = "\nLocation: %@"; +"calendar_format_hint" = "• Fill in event information\n• Will generate calendar event format\n• Can be imported to calendar app"; +"camera_permission_denied" = "Camera access has been denied. Please enable camera permission in Settings to use the scanner."; +"camera_permission_description" = "Required to scan QR codes and barcodes using your device's camera."; +"camera_permission_restricted" = "Camera access is restricted. Please check your device settings or contact your administrator."; +"camera_permission_title" = "Camera Permission Required"; +"camera_permission_unknown" = "Camera permission status is unknown. Please check your device settings."; +"cannot_generate_barcode" = "Cannot generate barcode"; +"cannot_generate_qr_code" = "Cannot generate QR code"; +"cannot_generate_qrcode" = "Cannot generate QR code"; +"cc_address" = "CC Address"; +"cc_email_placeholder" = "cc@example.com"; +"character_count" = "%d/%d"; +"character_type" = "Character Type:"; +"check_input_format" = "Please check input content format"; +"check_photo_permission" = "Check Photo Permission"; +"circle" = "Circle"; +"circuit" = "Circuit"; +"clear" = "Clear"; +"clear_history" = "Clear History"; +"clear_history_warning" = "This action will delete all history records and cannot be undone"; +"close" = "Close"; +"close_button" = "Close"; +"cloud" = "Cloud"; +"cloud_circle" = "Cloud Circle"; +"codabar_characters" = "Numbers (0-9), Letters (A-D), Special Characters (- + . / $ :)"; +"codabar_only_contains" = "Codabar can only contain numbers, letters A-D and special characters"; +"code_128_characters" = "All ASCII characters (0-127)"; +"code_128_format_hint" = "Please enter any ASCII characters"; +"code_128_only_contains" = "Code 128 can only contain ASCII characters"; +"code_39_characters" = "Letters (A-Z), Numbers (0-9), Space, Special Characters (- + . / $ ( ) %)"; +"code_39_format_hint" = "Please enter letters, numbers, spaces and special characters"; +"code_39_only_contains" = "Code 39 can only contain letters, numbers, spaces and special characters"; +"color_selection" = "Color Selection"; +"colors" = "Colors"; +"compact_card" = "Compact Card"; +"company" = "Company"; +"company_name" = "Company Name"; +"complete" = "Complete"; +"confirm" = "Confirm"; +"confirm_delete" = "Confirm Delete"; +"confirm_delete_record" = "Are you sure you want to delete this record?\nContent: %@"; +"connection_failed_check_network" = "Cannot connect to server, please check network connection"; +"contact" = "Contact"; +"contact_address" = "Address: %@"; +"contact_address_format" = "\nAddress: %@"; +"contact_address_placeholder" = "Enter address"; +"contact_birthday" = "Birthday: %@"; +"contact_company" = "Company: %@"; +"contact_company_format" = "\nCompany: %@"; +"contact_content_prefix" = "Contact: "; +"contact_email" = "Email: %@"; +"contact_email_format" = "\nEmail: %@"; +"contact_email_placeholder" = "user@example.com"; +"contact_format_hint" = "• Fill in contact information\n• Will generate vCard format\n• Can be imported to phone contacts"; +"contact_information" = "Contact Information"; +"contact_name" = "Name: %@"; +"contact_nickname" = "Nickname: %@"; +"contact_nickname_format" = " (%@)"; +"contact_note" = "Note: %@"; +"contact_note_format" = "\nNote: %@"; +"contact_note_placeholder" = "Enter note"; +"contact_phone" = "Phone: %@"; +"contact_phone_format" = "\nPhone: %@"; +"contact_phone_placeholder" = "+1 (555) 123-4567"; +"contact_title" = "Title: %@"; +"contact_title_format" = "\nTitle: %@"; +"contact_website" = "Website: %@"; +"contact_website_format" = "\nWebsite: %@"; +"contact_website_placeholder" = "https://example.com"; +"content" = "Content"; +"content_copied_to_clipboard" = "Content copied to clipboard"; +"content_input_area" = "Content Input Area"; +"content_length" = "Content Length: %d characters"; +"control_characters" = "Control Characters"; +"coordinate_format_details" = "• Latitude range: -90 to 90\n• Longitude range: -180 to 180\n• Use decimal points, e.g.: 40.7589"; +"coordinate_format_help" = "Coordinate Format Help"; +"copy" = "Copy"; +"copy_content" = "Copy Content"; +"core_data_load_failed" = "Core Data load failed: %@"; +"core_data_reload_failed" = "❌ Core Data reload failed: %@"; +"core_data_reload_success" = "✅ Core Data reload successful"; +"core_data_save_failed" = "❌ Core Data save failed: %@"; +"core_data_save_success" = "✅ Core Data save successful"; +"cornered_pixels" = "Cornered Pixels"; +"create_data_type" = "Create %@"; +"create_feature_description" = "Can manually create various types of QR codes and barcodes"; +"create_feature_title" = "Create Feature"; +"create_first_record" = "Create First Record"; +"create_qr_code" = "Create QR Code"; +"create_qr_code_document" = "Create QR Code Document"; +"created" = "Created"; +"crosshatch" = "Crosshatch"; +"current_language" = "Current Language: %@"; +"curve_pixel" = "Curve Pixel"; +"custom" = "Custom"; +"custom_logo" = "Custom Logo"; +"custom_qr_code_style" = "Custom QR Code Style"; +"custom_style" = "Custom Style"; +"cyan" = "Cyan"; +"data_content" = "Data Content"; +"data_matrix_characters" = "All ASCII characters (0-127)"; +"data_matrix_only_contains" = "Data Matrix can only contain ASCII characters"; +"data_type" = "Data Type"; +"data_type_created_successfully" = "%@ created successfully!"; +"day" = "Day"; +"days_ago" = "%d days ago"; +"debug" = "Debug"; +"decode_failed" = "Decode Failed"; +"decorate_code" = "Decorate Code"; +"delete_confirmation" = "Delete Confirmation"; +"denied" = "Denied"; +"description" = "Description"; +"detailed_address" = "Detailed Address"; +"detected_codes" = "Codes Detected"; +"diagonal" = "Diagonal"; +"diagonal_stripes" = "Diagonal Stripes"; +"diamond" = "Diamond"; +"donut" = "Donut"; +"dot_drag_horizontal" = "Horizontal Dot Drag"; +"dot_drag_vertical" = "Vertical Dot Drag"; +"dot_type" = "Dot Type"; +"dot_type_selection" = "Dot Type Selection"; +"dot_types" = "Dot Types"; +"drip_horizontal" = "Horizontal Drip"; +"drip_vertical" = "Vertical Drip"; +"ean_13_format_hint" = "Please enter 13 digits, e.g.: 1234567890123"; +"ean_13_must_be_13_digits" = "EAN-13 must be 13 digits"; +"ean_8_format_hint" = "Please enter 8 digits, e.g.: 12345678"; +"ean_8_must_be_8_digits" = "EAN-8 must be 8 digits"; +"edges" = "Edges"; +"email" = "Email"; +"email_address" = "Email Address"; +"email_bcc" = "BCC"; +"email_bcc_format" = "\nBCC: %@"; +"email_bcc_placeholder" = "bcc@example.com"; +"email_body" = "Email Body"; +"email_body_placeholder" = "Enter email body content..."; +"email_cc" = "CC"; +"email_cc_format" = "\nCC: %@"; +"email_cc_placeholder" = "cc@example.com"; +"email_content_format" = "Email: %@\nSubject: %@\nBody: %@"; +"email_format_hint" = "• Fill in email information\n• Will generate mailto: link\n• Users can click to open email app"; +"email_subject" = "Subject"; +"email_subject_placeholder" = "Email Subject"; +"encryption_type" = "Encryption Type"; +"end" = "End"; +"end_time" = "End Time"; +"end_time_must_be_after_start_time" = "End time must be after start time"; +"enter_artist_name" = "Enter artist name"; +"enter_description_content" = "Please enter description content..."; +"enter_email" = "Please enter email"; +"enter_email_body_content" = "Enter email body content..."; +"enter_long_text_content" = "Please enter long text content..."; +"enter_password" = "Please enter password"; +"enter_phone_number" = "Enter phone number, supports international format"; +"enter_sms_content" = "Enter SMS content, will generate sendable link"; +"enter_song_name" = "Enter song name"; +"enter_username" = "Please enter username"; +"error_details" = "❌ Error details: %@"; +"error_domain" = "❌ Error domain: %@"; +"error_hint" = "This is an error hint"; +"error_occurred" = "Error Occurred"; +"event" = "Event"; +"event_description" = "Event Description"; +"event_description_placeholder" = "Event Description"; +"event_location" = "Event Location"; +"event_location_placeholder" = "Meeting Location"; +"event_title" = "Event Title"; +"event_title_placeholder" = "Meeting Title"; +"existing_history_item" = "Existing History Item"; +"existing_style_data" = "Existing Style Data"; +"explode" = "Explode"; +"eye" = "Eye"; +"eye_type" = "Eye Type"; +"eye_type_selection" = "Eye Type Selection"; +"eyes" = "Eyes"; +"fabric_scissors" = "Fabric Scissors"; +"facebook" = "Facebook"; +"facebook_hint" = "Enter Facebook user ID or link"; +"facebook_placeholder" = "Username or link"; +"facebook_profile_id" = "Profile ID: %@"; +"favorite" = "Favorite"; +"favorites" = "Favorites"; +"features" = "Features"; +"field_format_incorrect" = "%@ format is incorrect"; +"field_required" = "%@ is required"; +"fireball" = "Fireball"; +"first_name" = "First Name"; +"flame" = "Flame"; +"flower" = "Flower"; +"foreground_color" = "Foreground Color"; +"format_checking" = "⚠ Format checking..."; +"format_correct" = "✓ Format correct"; +"format_error" = "Format Error"; +"format_help" = "Format Help"; +"format_instructions" = "Format Instructions"; +"formatted_content" = "Formatted: %@"; +"gear" = "Gear"; +"generate_various_codes" = "Generate QR codes for text, links, WiFi, contacts and more"; +"geolocation" = "Geolocation"; +"geolocation_coordinates" = "Latitude: %@\nLongitude: %@"; +"gmail" = "Gmail"; +"google_playstore" = "Google Play"; +"gray" = "Gray"; +"green" = "Green"; +"grid_2x2" = "2x2 Grid"; +"grid_3x3" = "3x3 Grid"; +"grid_4x4" = "4x4 Grid"; +"headlight" = "Headlight"; +"heart" = "Heart"; +"hexagon" = "Hexagon"; +"history_feature_description" = "Automatically save all scanned and created codes, support favorites and management"; +"history_feature_title" = "History Records"; +"history_records" = "History Records"; +"hole_punch" = "Hole Punch"; +"horizontal" = "Horizontal"; +"hours_ago" = "%d hours ago"; +"image_decode" = "Image Decode"; +"image_save_helper" = "Image Save Helper"; +"important_reminder" = "Important Reminder"; +"indigo" = "Indigo"; +"info_card" = "Information Card"; +"info_card_description" = "This is an information card for displaying important tips."; +"info_hint" = "This is an information hint"; +"input_13_digits" = "Input 13 digits"; +"input_14_digits" = "Input 14 digits"; +"input_8_digits" = "Input 8 digits"; +"input_any_characters" = "Input any characters"; +"input_artist_and_song_info" = "Input artist and song information..."; +"input_calendar_event_info" = "Input calendar event information..."; +"input_contact_info" = "Input contact information..."; +"input_facebook_user_id_or_link" = "Input Facebook user ID or link..."; +"input_hint" = "Input Hint"; +"input_instagram_username" = "Input Instagram username..."; +"input_letters_numbers" = "Input letters and numbers"; +"input_location_info" = "Input location information..."; +"input_phone_number" = "Input phone number..."; +"input_snapchat_info" = "Input Snapchat information..."; +"input_tiktok_info" = "Input TikTok information..."; +"input_viber_phone_number" = "Input Viber phone number (e.g.: +1234567890)..."; +"input_website_url" = "Input website URL..."; +"input_whatsapp_phone_number" = "Input WhatsApp phone number (e.g.: +1234567890)..."; +"input_wifi_info" = "Input WiFi information..."; +"input_x_info" = "Input X information..."; +"instagram" = "Instagram"; +"instagram_hint" = "Enter Instagram username"; +"instagram_placeholder" = "Username or link"; +"instagram_username" = "Username: %@"; +"item_format" = "Item %d"; +"itf_14_format_hint" = "Please enter 14 digits, e.g.: 12345678901234"; +"itf_14_must_be_14_digits" = "ITF-14 must be 14 digits"; +"itf_14_only_digits" = "ITF-14 can only contain digits"; +"job_title" = "Job Title"; +"just_now" = "Just now"; +"koala" = "Koala"; +"language_changes_info" = "Language changes will take effect immediately"; +"language_settings" = "Language Settings"; +"last_name" = "Last Name"; +"latitude" = "Latitude"; +"latitude_placeholder" = "40.7589"; +"leaf" = "Leaf"; +"learn_more" = "Learn More"; +"length_requirement" = "Length requirement: %d digits"; +"letters" = "Letters"; +"lime" = "Lime"; +"limited" = "Limited"; +"linked_in" = "LinkedIn"; +"linkedin" = "LinkedIn"; +"load_failed_retry" = "Load failed, please retry"; +"loading_data" = "Loading data..."; +"loading_state" = "Loading State"; +"location" = "Location"; +"location_content_format" = "Location: %@, %@"; +"location_format_hint" = "• Enter location name and coordinates\n• Will generate geo: link\n• Users can click to open maps app"; +"location_name" = "Location Name"; +"location_name_placeholder" = "e.g.: Times Square, New York"; +"logo" = "Logo"; +"logo_selection" = "Logo Selection"; +"long_text" = "Long Text"; +"longitude" = "Longitude"; +"longitude_placeholder" = "-73.9851"; +"magenta" = "Magenta"; +"manually_created" = "Manually Created"; +"maroon" = "Maroon"; +"max_characters_reached" = "Maximum characters reached"; +"maxi_code_characters" = "All ASCII characters (0-127)"; +"maxi_code_only_contains" = "MaxiCode can only contain ASCII characters"; +"medium" = "Medium"; +"minutes_ago" = "%d minutes ago"; +"month" = "Month"; +"name" = "Name"; +"navy" = "Navy"; +"near_character_limit" = "Near character limit"; +"network_error" = "Network Error"; +"network_name" = "Network Name"; +"new_this_month" = "New this month"; +"next_step" = "Next Step"; +"nickname" = "Nickname"; +"no_codes_detected_in_image" = "No QR codes or barcodes detected in image"; +"no_content_yet" = "No content here yet"; +"no_data" = "No Data"; +"no_encryption" = "No Encryption"; +"no_logo" = "No Logo"; +"none" = "None"; +"not_determined" = "Not Determined"; +"not_set" = "Not Set"; +"note" = "Note"; +"note_info" = "Note Information"; +"number" = "Number"; +"numbers" = "Numbers"; +"numbers_0_9" = "Numbers (0-9)"; +"olive" = "Olive"; +"open_link" = "Open Link"; +"open_system_settings" = "Open Settings"; +"operation_buttons" = "Operation Buttons"; +"orange" = "Orange"; +"original_content" = "Original Content"; +"parsed_info" = "Parsed Information"; +"password" = "Password"; +"password_set" = "Set"; +"paste" = "Paste"; +"paypal" = "PayPal"; +"pdf417_characters" = "All ASCII characters (0-127)"; +"pdf417_format_hint" = "Please enter any ASCII characters"; +"pdf417_only_contains" = "PDF417 can only contain ASCII characters"; +"peacock" = "Peacock"; +"permission_required" = "Permission Required"; +"permissions_description" = "This app requires certain permissions to function properly. You can manage these permissions here or in your device's Settings app."; +"permissions_info" = "Permissions Information"; +"phone" = "Phone"; +"phone_content_format" = "Phone: %@"; +"phone_format_hint" = "• Supports international format: +1 (555) 123-4567\n• Or local format: (555) 123-4567\n• Will generate tel: link"; +"phone_number" = "Phone Number"; +"phone_placeholder" = "+1 (555) 123-4567"; +"phone_type" = "Phone Type"; +"photo_permission" = "Photo Library Permission"; +"photo_permission_description" = "Required to save generated QR codes and barcodes to your photo library."; +"photo_permission_required" = "Photo library permission required to save images, please enable in Settings"; +"pinch" = "Pinch"; +"pink" = "Pink"; +"pinterest" = "Pinterest"; +"pixels" = "Pixels"; +"please_enter_content" = "Please enter content"; +"please_enter_valid_format" = "Please enter content that matches %@ format"; +"pointy" = "Pointy"; +"preview" = "Preview"; +"preview_area" = "Preview Area"; +"preview_url" = "Preview URL"; +"previous" = "Previous"; +"purple" = "Purple"; +"qr_code" = "QR Code"; +"qr_code_creator" = "QR Code Creator"; +"qr_code_detail" = "QR Code Detail"; +"qr_code_has_style" = "This QR code has custom style, tap to edit"; +"qr_code_image" = "QR Code Image"; +"qr_code_saved" = "QR Code Saved"; +"qr_code_saved_title" = "QR Code Saved"; +"qr_code_saved_to_photos" = "QR code saved to photos"; +"qr_code_type" = "QR Code Type"; +"qrcode_created_successfully" = "QR code created successfully!"; +"qrcode_type" = "QR Code Type"; +"quick_create_scan" = "Quickly create and scan QR codes"; +"razor" = "Razor"; +"red" = "Red"; +"removed_from_favorites" = "Removed from favorites"; +"request_camera_permission" = "Grant Camera Access"; +"rescan_button" = "Rescan"; +"reselect_image" = "Reselect Image"; +"restricted" = "Restricted"; +"retry" = "Retry"; +"return_home" = "Return Home"; +"rounded_end_indent" = "Rounded End Indent"; +"rounded_outer" = "Rounded Outer"; +"rounded_path" = "Rounded Path"; +"rounded_pointing_in" = "Rounded Pointing In"; +"rounded_pointing_out" = "Rounded Pointing Out"; +"rounded_rect" = "Rounded Rectangle"; +"rounded_triangle" = "Rounded Triangle"; +"sample_content" = "Sample content"; +"sample_form" = "Sample Form"; +"save_failed" = "Save failed: %@"; +"save_failed_error" = "Save failed: %@"; +"save_to_photos_button" = "Save to Photos Button"; +"saving" = "Saving..."; +"scan_error_message" = "Your device does not support scanning QR codes. Please use a device with a camera."; +"scan_error_title" = "Scan Error"; +"scan_feature_description" = "Support scanning QR codes and barcodes, automatically identify types and save to history"; +"scan_feature_title" = "Scan Feature"; +"scan_instruction" = "Place QR code or barcode in the frame"; +"scan_me" = "Scan Me"; +"scan_or_create_to_start" = "Scan QR codes or manually create to start recording"; +"scan_qr_code" = "Scan QR Code"; +"scan_recognize" = "Scan & Recognize"; +"scan_result" = "Scan Result:"; +"scan_this_barcode" = "Scan this barcode"; +"scan_this_qr_code" = "Scan this QR code"; +"scanned" = "Scanned"; +"scanner" = "Scanner"; +"scanner_title" = "Barcode Scanner"; +"scanning_line_style" = "Scanning Line Style"; +"search" = "Search"; +"search_history_records" = "Search history records..."; +"select_background_image" = "Select Background Image"; +"select_birthday" = "Select Birthday"; +"select_code_instruction" = "Tap green markers to select the code to decode"; +"select_date" = "Select Date"; +"select_date_and_time" = "Select Date and Time"; +"select_dot_type" = "Select Dot Type"; +"select_eye_type" = "Select Eye Type"; +"select_language" = "Select Language"; +"select_logo" = "Select Logo"; +"select_time" = "Select Time"; +"select_type" = "Select Type"; +"selected_tag_type" = "Selected Tag Type"; +"set_background_color" = "Set Background Color"; +"set_dot_style" = "Set Dot Style"; +"set_eye_shape" = "Set Eye Shape"; +"set_eye_style" = "Set Eye Style"; +"set_logo_if_selected" = "Set Logo if Selected"; +"share" = "Share"; +"share_barcode_image" = "Share Barcode Image"; +"share_button" = "Share Button"; +"sharp" = "Sharp"; +"shield" = "Shield"; +"shiny" = "Shiny"; +"silver" = "Silver"; +"simple_toolbar" = "Simple Toolbar"; +"sms" = "SMS"; +"sms_content" = "SMS Content"; +"sms_format_hint" = "• Enter phone number and SMS content\n• Will generate SMSTO: link\n• Users can click to send SMS directly"; +"sms_number_content" = "Number: %@\nContent: %@"; +"sms_placeholder" = "Enter SMS content"; +"snapchat" = "Snapchat"; +"snapchat_hint" = "Enter Snapchat username"; +"snapchat_placeholder" = "Username"; +"snapchat_username" = "Username: %@"; +"social" = "Social"; +"social_format_hint" = "• Enter social media information\n• Will generate social media links\n• Users can click to open social apps"; +"social_message" = "Message"; +"social_platform" = "Social Platform"; +"song_link_or_id" = "Song Link or ID"; +"song_name" = "Song Name"; +"special_characters" = "Special Characters"; +"spiky_circle" = "Spiky Circle"; +"spotify" = "Spotify"; +"spotify_hint" = "Enter Spotify song or playlist link"; +"spotify_placeholder" = "Song or playlist link"; +"spotify_search_query" = "Search: %@"; +"square" = "Square"; +"square_peg" = "Square Peg"; +"squircle" = "Squircle"; +"standard" = "Standard"; +"standard_card" = "Standard Card"; +"standard_card_description" = "This is a standard style card component"; +"star" = "Star"; +"start" = "Start"; +"start_scanning" = "Start Scanning"; +"start_time" = "Start Time"; +"stitch" = "Stitch"; +"strong" = "Strong"; +"style_classic" = "Classic Simple"; +"style_description_format" = "Foreground Color: %@, Background Color: %@, Dot Type: %@, Eye Type: %@"; +"style_logo_format" = ", Logo: %@"; +"style_minimal" = "Minimalist"; +"style_modern" = "Modern Tech"; +"style_neon" = "Neon Cool"; +"style_retro" = "Retro Style"; +"success_hint" = "This is a success hint"; +"surrounding_bars" = "Surrounding Bars"; +"symbols" = "Symbols"; +"system_settings" = "System Settings"; +"system_settings_description" = "You can also manage app permissions in your device's Settings app."; +"tag_type" = "Tag Type"; +"teal" = "Teal"; +"teardrop" = "Teardrop"; +"telegram" = "Telegram"; +"test_auto_select" = "Test Auto Select"; +"text" = "Text"; +"text_content" = "Text Content"; +"text_information" = "Text Information"; +"text_placeholder" = "Enter text content..."; +"tik_tok" = "TikTok"; +"tiktok" = "TikTok"; +"tiktok_hint" = "Enter TikTok username or full link"; +"tiktok_placeholder" = "Username"; +"tiktok_username" = "Username: %@"; +"time_setting_hint" = "Time Setting Hint"; +"time_validation_error" = "End time must be after start time"; +"tip" = "Tip"; +"title_name" = "Job Title"; +"toolbar_with_clear" = "Toolbar with Clear Button"; +"toolbar_with_copy_paste" = "Toolbar with Copy/Paste"; +"toolbar_with_navigation" = "Toolbar with Navigation"; +"total_users" = "Total Users"; +"twitter_hint" = "Enter X username or full link"; +"twitter_placeholder" = "Username"; +"twitter_username" = "Username: %@"; +"ufo" = "UFO"; +"ufo_rounded" = "Rounded UFO"; +"unfavorite" = "Unfavorite"; +"unknown" = "Unknown"; +"unknown_content" = "Unknown content"; +"upc_e_format_hint" = "Please enter 8 digits, e.g.: 12345678"; +"upc_e_must_be_8_digits" = "UPC-E must be 8 digits"; +"url" = "URL"; +"url_content_format" = "URL: %@"; +"url_format_hint" = "• You can enter full URL: https://www.example.com\n• Or enter domain: www.example.com\n• System will automatically add https:// prefix"; +"url_link" = "URL Link"; +"url_placeholder" = "https://www.example.com"; +"use_passed_qr_code_content" = "Use Passed QR Code Content"; +"use_pixel_shape" = "Use Pixel Shape"; +"user_id" = "User ID"; +"user_id_or_link" = "User ID or Link"; +"username" = "Username"; +"vertical" = "Vertical"; +"viber_hint" = "Enter Viber phone number"; +"viber_phone_number" = "Phone Number: %@"; +"viber_placeholder" = "Phone number"; +"view_history" = "View History"; +"vortex" = "Vortex"; +"warning_hint" = "This is a warning hint"; +"wave" = "Wave"; +"weak" = "Weak"; +"website" = "Website"; +"website_url" = "Website URL"; +"wex" = "Wex"; +"whats_app" = "WhatsApp"; +"whatsapp" = "WhatsApp"; +"whatsapp_hint" = "Enter WhatsApp message content"; +"whatsapp_phone_number" = "Phone Number: %@"; +"whatsapp_placeholder" = "Enter WhatsApp phone number"; +"white" = "White"; +"wifi" = "WiFi"; +"wifi_content_format" = "WiFi: %@ (%@)"; +"wifi_format_details" = "• Network name (SSID) is required\n• Password is optional, can be empty for no encryption\n• Will generate standard WiFi connection format"; +"wifi_network" = "Wi-Fi Network"; +"wifi_network_info" = "Network Name: %@\nEncryption Type: %@\nPassword: %@"; +"wifi_password" = "WiFi Password"; +"wifi_password_placeholder" = "WiFi Password"; +"x" = "X"; +"x_platform" = "X"; +"x_username" = "X Username"; +"year" = "Year"; +"yellow" = "Yellow"; +"yesterday" = "Yesterday"; +"youtube" = "YouTube"; + +// Missing keys (using reference as fallback) +"main_title" = "main_title"; +"qrcode" = "qrcode"; diff --git a/MyQrCode/ru.lproj/InfoPlist.strings b/MyQrCode/ru.lproj/InfoPlist.strings index 443bdfb..ca23f8e 100644 --- a/MyQrCode/ru.lproj/InfoPlist.strings +++ b/MyQrCode/ru.lproj/InfoPlist.strings @@ -7,3 +7,4 @@ "CFBundleDisplayName" = "MyQrCode"; "CFBundleName" = "MyQrCode"; +"NSContactsUsageDescription" = "Это приложение требует доступ к контактам для добавления контактной информации"; diff --git a/MyQrCode/ru.lproj/Localizable.strings b/MyQrCode/ru.lproj/Localizable.strings index 62963f5..e862e1e 100644 --- a/MyQrCode/ru.lproj/Localizable.strings +++ b/MyQrCode/ru.lproj/Localizable.strings @@ -760,3 +760,69 @@ // Missing keys (using reference as fallback) "main_title" = "main_title"; "qrcode" = "qrcode"; + + +"add_contact" = "Add Contact"; +"add_to_calendar" = "Add to Calendar"; +"app_open_failed" = "Cannot open app"; +"calendar_description" = "Description"; +"calendar_end_time" = "End Time"; +"calendar_event_add_failed" = "Failed to add event to calendar"; +"calendar_event_added_successfully" = "Event added to calendar successfully"; +"calendar_event_title" = "Event Title"; +"calendar_invalid_start_time" = "Invalid start time format"; +"calendar_location" = "Location"; +"calendar_permission_denied" = "Calendar permission required to add events"; +"calendar_permission_unknown" = "Calendar permission status unknown"; +"calendar_save_failed" = "Failed to save event"; +"calendar_start_time" = "Start Time"; +"call" = "Call"; +"camera" = "Camera"; +"connect_wifi" = "Connect WiFi"; +"contact_add_failed" = "Failed to add contact"; +"contact_added_successfully" = "Contact added to address book successfully"; +"contact_parse_failed" = "Failed to parse contact information"; +"contact_permission_denied" = "Contact permission required to add contact"; +"contact_permission_limited" = "Contact permission limited"; +"contact_permission_unknown" = "Contact permission status unknown"; +"copy_password" = "Copy Password"; +"email_app_failed" = "Cannot open email app"; +"invalid_phone_number" = "Invalid phone number"; +"message" = "Message"; +"open" = "Open"; +"open_facebook" = "Open Facebook"; +"open_instagram" = "Open Instagram"; +"open_spotify" = "Open Spotify"; +"open_viber" = "Open Viber"; +"open_whatsapp" = "Open WhatsApp"; +"open_x" = "Open X"; +"organization" = "Organization"; +"permission_denied_title" = "Permission Denied"; +"phone_call_failed" = "Failed to make phone call"; +"phone_call_initiated" = "Initiating phone call..."; +"phone_call_not_supported" = "Device does not support phone calls"; +"photo" = "Photo"; +"requesting_permission" = "Requesting..."; +"send_email" = "Send Email"; +"send_sms" = "Send SMS"; +"sms_app_failed" = "Failed to open SMS app"; +"sms_app_not_supported" = "Device does not support SMS"; +"sms_app_opened" = "SMS app opened"; +"sms_message" = "Message"; +"sms_phone_number" = "Phone Number"; +"spotify_album" = "Album: %@"; +"spotify_artist" = "Artist: %@"; +"spotify_playlist" = "Playlist: %@"; +"spotify_track" = "Track: %@"; +"title" = "Title"; +"wifi_already_connected" = "Already connected to this WiFi network"; +"wifi_connected_successfully" = "WiFi connected successfully!"; +"wifi_connecting" = "Attempting to connect to WiFi network..."; +"wifi_connection_failed" = "WiFi connection failed"; +"wifi_invalid_password" = "Invalid WiFi password format"; +"wifi_invalid_ssid" = "Invalid WiFi network name"; +"wifi_manual_setup_instruction" = "Please set up WiFi manually:\nNetwork Name: %@\nPassword: %@\n\nGo to: Settings > WiFi > Select Network > Enter Password"; +"wifi_opened_settings" = "Opened system WiFi settings"; +"wifi_password_copied" = "WiFi password copied to clipboard"; +"wifi_setup_instruction" = "Please go to Settings > WiFi to connect manually"; +"wifi_user_denied" = "User denied WiFi connection request"; \ No newline at end of file diff --git a/MyQrCode/ru.lproj/Localizable.strings.backup b/MyQrCode/ru.lproj/Localizable.strings.backup new file mode 100644 index 0000000..62963f5 --- /dev/null +++ b/MyQrCode/ru.lproj/Localizable.strings.backup @@ -0,0 +1,762 @@ +// App +"app_name" = "MyQrCode"; +"settings" = "Настройки"; +"scan" = "Сканировать"; +"create" = "Создать"; +"history" = "История"; +"about" = "О приложении"; + +// Language Manager +"chinese_language" = "Китайский"; +"system_language" = "Система"; + +// Input Component Factory +"input_any_text_content" = "Введите любой текстовый контент..."; +"input_url_content" = "Введите URL..."; +"input_email_content" = "Введите адрес электронной почты..."; +"input_phone_content" = "Введите номер телефона..."; +"input_sms_content" = "Введите SMS сообщение..."; +"input_wifi_content" = "Введите информацию WiFi..."; +"input_contact_content" = "Введите контактную информацию..."; +"input_calendar_content" = "Введите событие календаря..."; +"input_location_content" = "Введите информацию о местоположении..."; +"input_social_content" = "Введите информацию социальных сетей..."; + +// Input Field View +"input_field_title" = "Заголовок"; +"input_field_content" = "Содержание"; +"input_field_placeholder" = "Заполнитель"; + +// Input Hint View +"input_hint_title" = "Подсказка"; +"input_hint_content" = "Содержание подсказки"; + +// Input Title View +"input_title_text" = "Текст заголовка"; + +// Keyboard Toolbar View +"keyboard_toolbar_done" = "Готово"; +"keyboard_toolbar_cancel" = "Отмена"; + +// List View +"list_view_title" = "Вид списка"; +"list_view_empty" = "Список пуст"; + +// Picker View +"picker_view_title" = "Выбор"; +"picker_view_cancel" = "Отмена"; +"picker_view_done" = "Готово"; + +// QRCode Preview View +"qrcode_preview_title" = "Предварительный просмотр QR-кода"; +"qrcode_preview_save" = "Сохранить"; +"qrcode_preview_share" = "Поделиться"; + +// Utility Functions +"utility_success" = "Успех"; +"utility_error" = "Ошибка"; +"utility_warning" = "Предупреждение"; +"utility_info" = "Информация"; + +// Barcode Detail View +"barcode_detail_title" = "Детали штрих-кода"; +"barcode_detail_type" = "Тип"; +"barcode_detail_content" = "Содержание"; +"barcode_detail_copy" = "Копировать"; +"barcode_detail_share" = "Поделиться"; + +// Barcode Preview View +"barcode_preview_title" = "Предварительный просмотр штрих-кода"; +"barcode_preview_save" = "Сохранить"; +"barcode_preview_share" = "Поделиться"; + +// Code Type Selection View +"code_type_selection_title" = "Выбор типа кода"; +"code_type_text" = "Текст"; +"code_type_url" = "URL"; +"code_type_email" = "Электронная почта"; +"code_type_phone" = "Телефон"; +"code_type_sms" = "SMS"; +"code_type_wifi" = "WiFi"; +"code_type_contact" = "Контакт"; +"code_type_calendar" = "Календарь"; +"code_type_location" = "Местоположение"; +"code_type_social" = "Социальные сети"; + +// Create QR Code View +"create_qrcode_title" = "Создать QR-код"; +"create_qrcode_type" = "Тип"; +"create_qrcode_content" = "Содержание"; +"create_qrcode_generate" = "Создать"; + +// Create Code View +"create_code_title" = "Создать код"; +"create_code_type" = "Тип"; +"create_code_content" = "Содержание"; +"create_code_generate" = "Создать"; + +// History View +"history_title" = "История"; +"history_empty" = "История пуста"; +"history_delete" = "Удалить"; +"history_share" = "Поделиться"; +"history_copy" = "Копировать"; +"history_favorite" = "Избранное"; +"history_unfavorite" = "Убрать из избранного"; + +// QR Code Detail View +"qrcode_detail_title" = "Детали QR-кода"; +"qrcode_detail_type" = "Тип"; +"qrcode_detail_content" = "Содержание"; +"qrcode_detail_copy" = "Копировать"; +"qrcode_detail_share" = "Поделиться"; + +// Settings View +"select_app_language" = "Выберите язык приложения"; +"language" = "Язык"; +"app_info" = "Информация о приложении"; +"version" = "Версия"; +"version_number" = "1.0.0"; +"build_version" = "Сборка"; +"build_number" = "1"; +"app_permissions" = "Разрешения приложения"; +"manage_app_permissions" = "Управление разрешениями камеры и фотобиблиотеки для этого приложения."; +"privacy_policy" = "Политика конфиденциальности"; +"view_privacy_policy" = "Ознакомьтесь с нашей политикой конфиденциальности и практиками обработки данных."; + +// App Permissions View +"camera_permission" = "Разрешение камеры"; +"photo_library_permission" = "Разрешение фотобиблиотеки"; +"permission_authorized" = "Разрешено"; +"permission_denied" = "Отклонено"; +"permission_restricted" = "Ограничено"; +"permission_not_determined" = "Не определено"; +"permission_limited" = "Ограничено"; +"request_permission" = "Запросить разрешение"; +"open_settings" = "Открыть настройки"; +"permission_granted" = "Разрешение предоставлено"; + +// Navigation +"back" = "Назад"; +"next" = "Далее"; +"cancel" = "Отмена"; +"save" = "Сохранить"; +"delete" = "Удалить"; +"edit" = "Редактировать"; +"done" = "Готово"; + +// Common +"ok" = "OK"; +"yes" = "Да"; +"no" = "Нет"; +"loading" = "Загрузка..."; +"error" = "Ошибка"; +"success" = "Успех"; +"warning" = "Предупреждение"; +"info" = "Информация"; + +// Pagination +"load_more" = "Загрузить еще"; +"no_more_data" = "Больше данных нет"; +"loading_more" = "Загрузка еще..."; + +// History +"no_history_records" = "Записей истории нет"; + +// Missing keys (using English as fallback) +"abstract" = "Abstract"; +"actions" = "Actions"; +"add_content" = "Add Content"; +"add_to_image" = "Add to Image"; +"add_to_picture" = "Add to Picture"; +"add_to_picture_button" = "Add to Picture Button"; +"add_to_picture_title" = "Add to Picture"; +"added_to_favorites" = "Added to favorites"; +"address" = "Address"; +"all" = "All"; +"all_ascii" = "All ASCII"; +"allowed_characters" = "Allowed characters: %@"; +"app_description" = "Scan QR codes and barcodes with ease"; +"app_description_long" = "QR Scanner is a powerful QR code and barcode scanning app that supports multiple barcode format recognition and creation."; +"app_title" = "MyQrCode"; +"arc" = "Arc"; +"architecture_mismatch_detected" = "🔄 Architecture mismatch detected, deleting existing database file"; +"arrow" = "Arrow"; +"artist" = "Artist"; +"authorized" = "Authorized"; +"auto_result_1s" = "Result will be shown in 1 second"; +"aztec_characters" = "All ASCII characters (0-127)"; +"aztec_only_contains" = "Aztec can only contain ASCII characters"; +"background_color" = "Background Color"; +"barcode" = "Barcode"; +"barcode_content" = "Barcode Content"; +"barcode_detail" = "Barcode Detail"; +"barcode_format_incorrect" = "Barcode format incorrect"; +"barcode_type" = "Barcode Type"; +"bars_horizontal" = "Horizontal Bars"; +"bars_vertical" = "Vertical Bars"; +"basic_info" = "Basic Information"; +"bcc_address" = "BCC Address"; +"bcc_email_placeholder" = "bcc@example.com"; +"birthday" = "Birthday"; +"birthday_format" = "%@-%@-%@"; +"black" = "Black"; +"blob" = "Blob"; +"blue" = "Blue"; +"brown" = "Brown"; +"calendar" = "Calendar"; +"calendar_content_format" = "Event: %@"; +"calendar_event" = "Calendar Event"; +"calendar_event_description" = "\nDescription: %@"; +"calendar_event_info" = "Event: %@\nStart: %@\nEnd: %@"; +"calendar_event_location" = "\nLocation: %@"; +"calendar_format_hint" = "• Fill in event information\n• Will generate calendar event format\n• Can be imported to calendar app"; +"camera_permission_denied" = "Camera access has been denied. Please enable camera permission in Settings to use the scanner."; +"camera_permission_description" = "Required to scan QR codes and barcodes using your device's camera."; +"camera_permission_restricted" = "Camera access is restricted. Please check your device settings or contact your administrator."; +"camera_permission_title" = "Camera Permission Required"; +"camera_permission_unknown" = "Camera permission status is unknown. Please check your device settings."; +"cannot_generate_barcode" = "Cannot generate barcode"; +"cannot_generate_qr_code" = "Cannot generate QR code"; +"cannot_generate_qrcode" = "Cannot generate QR code"; +"cc_address" = "CC Address"; +"cc_email_placeholder" = "cc@example.com"; +"character_count" = "%d/%d"; +"character_type" = "Character Type:"; +"check_input_format" = "Please check input content format"; +"check_photo_permission" = "Check Photo Permission"; +"circle" = "Circle"; +"circuit" = "Circuit"; +"clear" = "Clear"; +"clear_history" = "Clear History"; +"clear_history_warning" = "This action will delete all history records and cannot be undone"; +"close" = "Close"; +"close_button" = "Close"; +"cloud" = "Cloud"; +"cloud_circle" = "Cloud Circle"; +"codabar_characters" = "Numbers (0-9), Letters (A-D), Special Characters (- + . / $ :)"; +"codabar_only_contains" = "Codabar can only contain numbers, letters A-D and special characters"; +"code_128_characters" = "All ASCII characters (0-127)"; +"code_128_format_hint" = "Please enter any ASCII characters"; +"code_128_only_contains" = "Code 128 can only contain ASCII characters"; +"code_39_characters" = "Letters (A-Z), Numbers (0-9), Space, Special Characters (- + . / $ ( ) %)"; +"code_39_format_hint" = "Please enter letters, numbers, spaces and special characters"; +"code_39_only_contains" = "Code 39 can only contain letters, numbers, spaces and special characters"; +"color_selection" = "Color Selection"; +"colors" = "Colors"; +"compact_card" = "Compact Card"; +"company" = "Company"; +"company_name" = "Company Name"; +"complete" = "Complete"; +"confirm" = "Confirm"; +"confirm_delete" = "Confirm Delete"; +"confirm_delete_record" = "Are you sure you want to delete this record?\nContent: %@"; +"connection_failed_check_network" = "Cannot connect to server, please check network connection"; +"contact" = "Contact"; +"contact_address" = "Address: %@"; +"contact_address_format" = "\nAddress: %@"; +"contact_address_placeholder" = "Enter address"; +"contact_birthday" = "Birthday: %@"; +"contact_company" = "Company: %@"; +"contact_company_format" = "\nCompany: %@"; +"contact_content_prefix" = "Contact: "; +"contact_email" = "Email: %@"; +"contact_email_format" = "\nEmail: %@"; +"contact_email_placeholder" = "user@example.com"; +"contact_format_hint" = "• Fill in contact information\n• Will generate vCard format\n• Can be imported to phone contacts"; +"contact_information" = "Contact Information"; +"contact_name" = "Name: %@"; +"contact_nickname" = "Nickname: %@"; +"contact_nickname_format" = " (%@)"; +"contact_note" = "Note: %@"; +"contact_note_format" = "\nNote: %@"; +"contact_note_placeholder" = "Enter note"; +"contact_phone" = "Phone: %@"; +"contact_phone_format" = "\nPhone: %@"; +"contact_phone_placeholder" = "+1 (555) 123-4567"; +"contact_title" = "Title: %@"; +"contact_title_format" = "\nTitle: %@"; +"contact_website" = "Website: %@"; +"contact_website_format" = "\nWebsite: %@"; +"contact_website_placeholder" = "https://example.com"; +"content" = "Content"; +"content_copied_to_clipboard" = "Content copied to clipboard"; +"content_input_area" = "Content Input Area"; +"content_length" = "Content Length: %d characters"; +"control_characters" = "Control Characters"; +"coordinate_format_details" = "• Latitude range: -90 to 90\n• Longitude range: -180 to 180\n• Use decimal points, e.g.: 40.7589"; +"coordinate_format_help" = "Coordinate Format Help"; +"copy" = "Copy"; +"copy_content" = "Copy Content"; +"core_data_load_failed" = "Core Data load failed: %@"; +"core_data_reload_failed" = "❌ Core Data reload failed: %@"; +"core_data_reload_success" = "✅ Core Data reload successful"; +"core_data_save_failed" = "❌ Core Data save failed: %@"; +"core_data_save_success" = "✅ Core Data save successful"; +"cornered_pixels" = "Cornered Pixels"; +"create_data_type" = "Create %@"; +"create_feature_description" = "Can manually create various types of QR codes and barcodes"; +"create_feature_title" = "Create Feature"; +"create_first_record" = "Create First Record"; +"create_qr_code" = "Create QR Code"; +"create_qr_code_document" = "Create QR Code Document"; +"created" = "Created"; +"crosshatch" = "Crosshatch"; +"current_language" = "Current Language: %@"; +"curve_pixel" = "Curve Pixel"; +"custom" = "Custom"; +"custom_logo" = "Custom Logo"; +"custom_qr_code_style" = "Custom QR Code Style"; +"custom_style" = "Custom Style"; +"cyan" = "Cyan"; +"data_content" = "Data Content"; +"data_matrix_characters" = "All ASCII characters (0-127)"; +"data_matrix_only_contains" = "Data Matrix can only contain ASCII characters"; +"data_type" = "Data Type"; +"data_type_created_successfully" = "%@ created successfully!"; +"day" = "Day"; +"days_ago" = "%d days ago"; +"debug" = "Debug"; +"decode_failed" = "Decode Failed"; +"decorate_code" = "Decorate Code"; +"delete_confirmation" = "Delete Confirmation"; +"denied" = "Denied"; +"description" = "Description"; +"detailed_address" = "Detailed Address"; +"detected_codes" = "Codes Detected"; +"diagonal" = "Diagonal"; +"diagonal_stripes" = "Diagonal Stripes"; +"diamond" = "Diamond"; +"donut" = "Donut"; +"dot_drag_horizontal" = "Horizontal Dot Drag"; +"dot_drag_vertical" = "Vertical Dot Drag"; +"dot_type" = "Dot Type"; +"dot_type_selection" = "Dot Type Selection"; +"dot_types" = "Dot Types"; +"drip_horizontal" = "Horizontal Drip"; +"drip_vertical" = "Vertical Drip"; +"ean_13_format_hint" = "Please enter 13 digits, e.g.: 1234567890123"; +"ean_13_must_be_13_digits" = "EAN-13 must be 13 digits"; +"ean_8_format_hint" = "Please enter 8 digits, e.g.: 12345678"; +"ean_8_must_be_8_digits" = "EAN-8 must be 8 digits"; +"edges" = "Edges"; +"email" = "Email"; +"email_address" = "Email Address"; +"email_bcc" = "BCC"; +"email_bcc_format" = "\nBCC: %@"; +"email_bcc_placeholder" = "bcc@example.com"; +"email_body" = "Email Body"; +"email_body_placeholder" = "Enter email body content..."; +"email_cc" = "CC"; +"email_cc_format" = "\nCC: %@"; +"email_cc_placeholder" = "cc@example.com"; +"email_content_format" = "Email: %@\nSubject: %@\nBody: %@"; +"email_format_hint" = "• Fill in email information\n• Will generate mailto: link\n• Users can click to open email app"; +"email_subject" = "Subject"; +"email_subject_placeholder" = "Email Subject"; +"encryption_type" = "Encryption Type"; +"end" = "End"; +"end_time" = "End Time"; +"end_time_must_be_after_start_time" = "End time must be after start time"; +"enter_artist_name" = "Enter artist name"; +"enter_description_content" = "Please enter description content..."; +"enter_email" = "Please enter email"; +"enter_email_body_content" = "Enter email body content..."; +"enter_long_text_content" = "Please enter long text content..."; +"enter_password" = "Please enter password"; +"enter_phone_number" = "Enter phone number, supports international format"; +"enter_sms_content" = "Enter SMS content, will generate sendable link"; +"enter_song_name" = "Enter song name"; +"enter_username" = "Please enter username"; +"error_details" = "❌ Error details: %@"; +"error_domain" = "❌ Error domain: %@"; +"error_hint" = "This is an error hint"; +"error_occurred" = "Error Occurred"; +"event" = "Event"; +"event_description" = "Event Description"; +"event_description_placeholder" = "Event Description"; +"event_location" = "Event Location"; +"event_location_placeholder" = "Meeting Location"; +"event_title" = "Event Title"; +"event_title_placeholder" = "Meeting Title"; +"existing_history_item" = "Existing History Item"; +"existing_style_data" = "Existing Style Data"; +"explode" = "Explode"; +"eye" = "Eye"; +"eye_type" = "Eye Type"; +"eye_type_selection" = "Eye Type Selection"; +"eyes" = "Eyes"; +"fabric_scissors" = "Fabric Scissors"; +"facebook" = "Facebook"; +"facebook_hint" = "Enter Facebook user ID or link"; +"facebook_placeholder" = "Username or link"; +"facebook_profile_id" = "Profile ID: %@"; +"favorite" = "Favorite"; +"favorites" = "Favorites"; +"features" = "Features"; +"field_format_incorrect" = "%@ format is incorrect"; +"field_required" = "%@ is required"; +"fireball" = "Fireball"; +"first_name" = "First Name"; +"flame" = "Flame"; +"flower" = "Flower"; +"foreground_color" = "Foreground Color"; +"format_checking" = "⚠ Format checking..."; +"format_correct" = "✓ Format correct"; +"format_error" = "Format Error"; +"format_help" = "Format Help"; +"format_instructions" = "Format Instructions"; +"formatted_content" = "Formatted: %@"; +"gear" = "Gear"; +"generate_various_codes" = "Generate QR codes for text, links, WiFi, contacts and more"; +"geolocation" = "Geolocation"; +"geolocation_coordinates" = "Latitude: %@\nLongitude: %@"; +"gmail" = "Gmail"; +"google_playstore" = "Google Play"; +"gray" = "Gray"; +"green" = "Green"; +"grid_2x2" = "2x2 Grid"; +"grid_3x3" = "3x3 Grid"; +"grid_4x4" = "4x4 Grid"; +"headlight" = "Headlight"; +"heart" = "Heart"; +"hexagon" = "Hexagon"; +"history_feature_description" = "Automatically save all scanned and created codes, support favorites and management"; +"history_feature_title" = "History Records"; +"history_records" = "History Records"; +"hole_punch" = "Hole Punch"; +"horizontal" = "Horizontal"; +"hours_ago" = "%d hours ago"; +"image_decode" = "Image Decode"; +"image_save_helper" = "Image Save Helper"; +"important_reminder" = "Important Reminder"; +"indigo" = "Indigo"; +"info_card" = "Information Card"; +"info_card_description" = "This is an information card for displaying important tips."; +"info_hint" = "This is an information hint"; +"input_13_digits" = "Input 13 digits"; +"input_14_digits" = "Input 14 digits"; +"input_8_digits" = "Input 8 digits"; +"input_any_characters" = "Input any characters"; +"input_artist_and_song_info" = "Input artist and song information..."; +"input_calendar_event_info" = "Input calendar event information..."; +"input_contact_info" = "Input contact information..."; +"input_facebook_user_id_or_link" = "Input Facebook user ID or link..."; +"input_hint" = "Input Hint"; +"input_instagram_username" = "Input Instagram username..."; +"input_letters_numbers" = "Input letters and numbers"; +"input_location_info" = "Input location information..."; +"input_phone_number" = "Input phone number..."; +"input_snapchat_info" = "Input Snapchat information..."; +"input_tiktok_info" = "Input TikTok information..."; +"input_viber_phone_number" = "Input Viber phone number (e.g.: +1234567890)..."; +"input_website_url" = "Input website URL..."; +"input_whatsapp_phone_number" = "Input WhatsApp phone number (e.g.: +1234567890)..."; +"input_wifi_info" = "Input WiFi information..."; +"input_x_info" = "Input X information..."; +"instagram" = "Instagram"; +"instagram_hint" = "Enter Instagram username"; +"instagram_placeholder" = "Username or link"; +"instagram_username" = "Username: %@"; +"item_format" = "Item %d"; +"itf_14_format_hint" = "Please enter 14 digits, e.g.: 12345678901234"; +"itf_14_must_be_14_digits" = "ITF-14 must be 14 digits"; +"itf_14_only_digits" = "ITF-14 can only contain digits"; +"job_title" = "Job Title"; +"just_now" = "Just now"; +"koala" = "Koala"; +"language_changes_info" = "Language changes will take effect immediately"; +"language_settings" = "Language Settings"; +"last_name" = "Last Name"; +"latitude" = "Latitude"; +"latitude_placeholder" = "40.7589"; +"leaf" = "Leaf"; +"learn_more" = "Learn More"; +"length_requirement" = "Length requirement: %d digits"; +"letters" = "Letters"; +"lime" = "Lime"; +"limited" = "Limited"; +"linked_in" = "LinkedIn"; +"linkedin" = "LinkedIn"; +"load_failed_retry" = "Load failed, please retry"; +"loading_data" = "Loading data..."; +"loading_state" = "Loading State"; +"location" = "Location"; +"location_content_format" = "Location: %@, %@"; +"location_format_hint" = "• Enter location name and coordinates\n• Will generate geo: link\n• Users can click to open maps app"; +"location_name" = "Location Name"; +"location_name_placeholder" = "e.g.: Times Square, New York"; +"logo" = "Logo"; +"logo_selection" = "Logo Selection"; +"long_text" = "Long Text"; +"longitude" = "Longitude"; +"longitude_placeholder" = "-73.9851"; +"magenta" = "Magenta"; +"manually_created" = "Manually Created"; +"maroon" = "Maroon"; +"max_characters_reached" = "Maximum characters reached"; +"maxi_code_characters" = "All ASCII characters (0-127)"; +"maxi_code_only_contains" = "MaxiCode can only contain ASCII characters"; +"medium" = "Medium"; +"minutes_ago" = "%d minutes ago"; +"month" = "Month"; +"name" = "Name"; +"navy" = "Navy"; +"near_character_limit" = "Near character limit"; +"network_error" = "Network Error"; +"network_name" = "Network Name"; +"new_this_month" = "New this month"; +"next_step" = "Next Step"; +"nickname" = "Nickname"; +"no_codes_detected_in_image" = "No QR codes or barcodes detected in image"; +"no_content_yet" = "No content here yet"; +"no_data" = "No Data"; +"no_encryption" = "No Encryption"; +"no_logo" = "No Logo"; +"none" = "None"; +"not_determined" = "Not Determined"; +"not_set" = "Not Set"; +"note" = "Note"; +"note_info" = "Note Information"; +"number" = "Number"; +"numbers" = "Numbers"; +"numbers_0_9" = "Numbers (0-9)"; +"olive" = "Olive"; +"open_link" = "Open Link"; +"open_system_settings" = "Open Settings"; +"operation_buttons" = "Operation Buttons"; +"orange" = "Orange"; +"original_content" = "Original Content"; +"parsed_info" = "Parsed Information"; +"password" = "Password"; +"password_set" = "Set"; +"paste" = "Paste"; +"paypal" = "PayPal"; +"pdf417_characters" = "All ASCII characters (0-127)"; +"pdf417_format_hint" = "Please enter any ASCII characters"; +"pdf417_only_contains" = "PDF417 can only contain ASCII characters"; +"peacock" = "Peacock"; +"permission_required" = "Permission Required"; +"permissions_description" = "This app requires certain permissions to function properly. You can manage these permissions here or in your device's Settings app."; +"permissions_info" = "Permissions Information"; +"phone" = "Phone"; +"phone_content_format" = "Phone: %@"; +"phone_format_hint" = "• Supports international format: +1 (555) 123-4567\n• Or local format: (555) 123-4567\n• Will generate tel: link"; +"phone_number" = "Phone Number"; +"phone_placeholder" = "+1 (555) 123-4567"; +"phone_type" = "Phone Type"; +"photo_permission" = "Photo Library Permission"; +"photo_permission_description" = "Required to save generated QR codes and barcodes to your photo library."; +"photo_permission_required" = "Photo library permission required to save images, please enable in Settings"; +"pinch" = "Pinch"; +"pink" = "Pink"; +"pinterest" = "Pinterest"; +"pixels" = "Pixels"; +"please_enter_content" = "Please enter content"; +"please_enter_valid_format" = "Please enter content that matches %@ format"; +"pointy" = "Pointy"; +"preview" = "Preview"; +"preview_area" = "Preview Area"; +"preview_url" = "Preview URL"; +"previous" = "Previous"; +"purple" = "Purple"; +"qr_code" = "QR Code"; +"qr_code_creator" = "QR Code Creator"; +"qr_code_detail" = "QR Code Detail"; +"qr_code_has_style" = "This QR code has custom style, tap to edit"; +"qr_code_image" = "QR Code Image"; +"qr_code_saved" = "QR Code Saved"; +"qr_code_saved_title" = "QR Code Saved"; +"qr_code_saved_to_photos" = "QR code saved to photos"; +"qr_code_type" = "QR Code Type"; +"qrcode_created_successfully" = "QR code created successfully!"; +"qrcode_type" = "QR Code Type"; +"quick_create_scan" = "Quickly create and scan QR codes"; +"razor" = "Razor"; +"red" = "Red"; +"removed_from_favorites" = "Removed from favorites"; +"request_camera_permission" = "Grant Camera Access"; +"rescan_button" = "Rescan"; +"reselect_image" = "Reselect Image"; +"restricted" = "Restricted"; +"retry" = "Retry"; +"return_home" = "Return Home"; +"rounded_end_indent" = "Rounded End Indent"; +"rounded_outer" = "Rounded Outer"; +"rounded_path" = "Rounded Path"; +"rounded_pointing_in" = "Rounded Pointing In"; +"rounded_pointing_out" = "Rounded Pointing Out"; +"rounded_rect" = "Rounded Rectangle"; +"rounded_triangle" = "Rounded Triangle"; +"sample_content" = "Sample content"; +"sample_form" = "Sample Form"; +"save_failed" = "Save failed: %@"; +"save_failed_error" = "Save failed: %@"; +"save_to_photos_button" = "Save to Photos Button"; +"saving" = "Saving..."; +"scan_error_message" = "Your device does not support scanning QR codes. Please use a device with a camera."; +"scan_error_title" = "Scan Error"; +"scan_feature_description" = "Support scanning QR codes and barcodes, automatically identify types and save to history"; +"scan_feature_title" = "Scan Feature"; +"scan_instruction" = "Place QR code or barcode in the frame"; +"scan_me" = "Scan Me"; +"scan_or_create_to_start" = "Scan QR codes or manually create to start recording"; +"scan_qr_code" = "Scan QR Code"; +"scan_recognize" = "Scan & Recognize"; +"scan_result" = "Scan Result:"; +"scan_this_barcode" = "Scan this barcode"; +"scan_this_qr_code" = "Scan this QR code"; +"scanned" = "Scanned"; +"scanner" = "Scanner"; +"scanner_title" = "Barcode Scanner"; +"scanning_line_style" = "Scanning Line Style"; +"search" = "Search"; +"search_history_records" = "Search history records..."; +"select_background_image" = "Select Background Image"; +"select_birthday" = "Select Birthday"; +"select_code_instruction" = "Tap green markers to select the code to decode"; +"select_date" = "Select Date"; +"select_date_and_time" = "Select Date and Time"; +"select_dot_type" = "Select Dot Type"; +"select_eye_type" = "Select Eye Type"; +"select_language" = "Select Language"; +"select_logo" = "Select Logo"; +"select_time" = "Select Time"; +"select_type" = "Select Type"; +"selected_tag_type" = "Selected Tag Type"; +"set_background_color" = "Set Background Color"; +"set_dot_style" = "Set Dot Style"; +"set_eye_shape" = "Set Eye Shape"; +"set_eye_style" = "Set Eye Style"; +"set_logo_if_selected" = "Set Logo if Selected"; +"share" = "Share"; +"share_barcode_image" = "Share Barcode Image"; +"share_button" = "Share Button"; +"sharp" = "Sharp"; +"shield" = "Shield"; +"shiny" = "Shiny"; +"silver" = "Silver"; +"simple_toolbar" = "Simple Toolbar"; +"sms" = "SMS"; +"sms_content" = "SMS Content"; +"sms_format_hint" = "• Enter phone number and SMS content\n• Will generate SMSTO: link\n• Users can click to send SMS directly"; +"sms_number_content" = "Number: %@\nContent: %@"; +"sms_placeholder" = "Enter SMS content"; +"snapchat" = "Snapchat"; +"snapchat_hint" = "Enter Snapchat username"; +"snapchat_placeholder" = "Username"; +"snapchat_username" = "Username: %@"; +"social" = "Social"; +"social_format_hint" = "• Enter social media information\n• Will generate social media links\n• Users can click to open social apps"; +"social_message" = "Message"; +"social_platform" = "Social Platform"; +"song_link_or_id" = "Song Link or ID"; +"song_name" = "Song Name"; +"special_characters" = "Special Characters"; +"spiky_circle" = "Spiky Circle"; +"spotify" = "Spotify"; +"spotify_hint" = "Enter Spotify song or playlist link"; +"spotify_placeholder" = "Song or playlist link"; +"spotify_search_query" = "Search: %@"; +"square" = "Square"; +"square_peg" = "Square Peg"; +"squircle" = "Squircle"; +"standard" = "Standard"; +"standard_card" = "Standard Card"; +"standard_card_description" = "This is a standard style card component"; +"star" = "Star"; +"start" = "Start"; +"start_scanning" = "Start Scanning"; +"start_time" = "Start Time"; +"stitch" = "Stitch"; +"strong" = "Strong"; +"style_classic" = "Classic Simple"; +"style_description_format" = "Foreground Color: %@, Background Color: %@, Dot Type: %@, Eye Type: %@"; +"style_logo_format" = ", Logo: %@"; +"style_minimal" = "Minimalist"; +"style_modern" = "Modern Tech"; +"style_neon" = "Neon Cool"; +"style_retro" = "Retro Style"; +"success_hint" = "This is a success hint"; +"surrounding_bars" = "Surrounding Bars"; +"symbols" = "Symbols"; +"system_settings" = "System Settings"; +"system_settings_description" = "You can also manage app permissions in your device's Settings app."; +"tag_type" = "Tag Type"; +"teal" = "Teal"; +"teardrop" = "Teardrop"; +"telegram" = "Telegram"; +"test_auto_select" = "Test Auto Select"; +"text" = "Text"; +"text_content" = "Text Content"; +"text_information" = "Text Information"; +"text_placeholder" = "Enter text content..."; +"tik_tok" = "TikTok"; +"tiktok" = "TikTok"; +"tiktok_hint" = "Enter TikTok username or full link"; +"tiktok_placeholder" = "Username"; +"tiktok_username" = "Username: %@"; +"time_setting_hint" = "Time Setting Hint"; +"time_validation_error" = "End time must be after start time"; +"tip" = "Tip"; +"title_name" = "Job Title"; +"toolbar_with_clear" = "Toolbar with Clear Button"; +"toolbar_with_copy_paste" = "Toolbar with Copy/Paste"; +"toolbar_with_navigation" = "Toolbar with Navigation"; +"total_users" = "Total Users"; +"twitter_hint" = "Enter X username or full link"; +"twitter_placeholder" = "Username"; +"twitter_username" = "Username: %@"; +"ufo" = "UFO"; +"ufo_rounded" = "Rounded UFO"; +"unfavorite" = "Unfavorite"; +"unknown" = "Unknown"; +"unknown_content" = "Unknown content"; +"upc_e_format_hint" = "Please enter 8 digits, e.g.: 12345678"; +"upc_e_must_be_8_digits" = "UPC-E must be 8 digits"; +"url" = "URL"; +"url_content_format" = "URL: %@"; +"url_format_hint" = "• You can enter full URL: https://www.example.com\n• Or enter domain: www.example.com\n• System will automatically add https:// prefix"; +"url_link" = "URL Link"; +"url_placeholder" = "https://www.example.com"; +"use_passed_qr_code_content" = "Use Passed QR Code Content"; +"use_pixel_shape" = "Use Pixel Shape"; +"user_id" = "User ID"; +"user_id_or_link" = "User ID or Link"; +"username" = "Username"; +"vertical" = "Vertical"; +"viber_hint" = "Enter Viber phone number"; +"viber_phone_number" = "Phone Number: %@"; +"viber_placeholder" = "Phone number"; +"view_history" = "View History"; +"vortex" = "Vortex"; +"warning_hint" = "This is a warning hint"; +"wave" = "Wave"; +"weak" = "Weak"; +"website" = "Website"; +"website_url" = "Website URL"; +"wex" = "Wex"; +"whats_app" = "WhatsApp"; +"whatsapp" = "WhatsApp"; +"whatsapp_hint" = "Enter WhatsApp message content"; +"whatsapp_phone_number" = "Phone Number: %@"; +"whatsapp_placeholder" = "Enter WhatsApp phone number"; +"white" = "White"; +"wifi" = "WiFi"; +"wifi_content_format" = "WiFi: %@ (%@)"; +"wifi_format_details" = "• Network name (SSID) is required\n• Password is optional, can be empty for no encryption\n• Will generate standard WiFi connection format"; +"wifi_network" = "Wi-Fi Network"; +"wifi_network_info" = "Network Name: %@\nEncryption Type: %@\nPassword: %@"; +"wifi_password" = "WiFi Password"; +"wifi_password_placeholder" = "WiFi Password"; +"x" = "X"; +"x_platform" = "X"; +"x_username" = "X Username"; +"year" = "Year"; +"yellow" = "Yellow"; +"yesterday" = "Yesterday"; +"youtube" = "YouTube"; + +// Missing keys (using reference as fallback) +"main_title" = "main_title"; +"qrcode" = "qrcode"; diff --git a/MyQrCode/th.lproj/InfoPlist.strings b/MyQrCode/th.lproj/InfoPlist.strings index 443bdfb..d793124 100644 --- a/MyQrCode/th.lproj/InfoPlist.strings +++ b/MyQrCode/th.lproj/InfoPlist.strings @@ -7,3 +7,4 @@ "CFBundleDisplayName" = "MyQrCode"; "CFBundleName" = "MyQrCode"; +"NSContactsUsageDescription" = "แอปนี้ต้องการเข้าถึงรายชื่อติดต่อเพื่อเพิ่มข้อมูลรายชื่อติดต่อ"; diff --git a/MyQrCode/th.lproj/Localizable.strings b/MyQrCode/th.lproj/Localizable.strings index e67250b..15b5f30 100644 --- a/MyQrCode/th.lproj/Localizable.strings +++ b/MyQrCode/th.lproj/Localizable.strings @@ -802,6 +802,22 @@ "wifi_invalid_ssid" = "ชื่อเครือข่าย WiFi ไม่ถูกต้อง"; "wifi_invalid_password" = "รูปแบบรหัสผ่าน WiFi ไม่ถูกต้อง"; +// ติดต่อที่เกี่ยวข้อง +"contact_added_successfully" = "เพิ่มรายชื่อติดต่อในสมุดที่อยู่สำเร็จแล้ว"; +"contact_add_failed" = "เพิ่มรายชื่อติดต่อล้มเหลว"; +"contact_permission_denied" = "ต้องการสิทธิ์สมุดที่อยู่เพื่อเพิ่มรายชื่อติดต่อ"; +"contact_permission_limited" = "สิทธิ์สมุดที่อยู่ถูกจำกัด"; +"contact_permission_unknown" = "สถานะสิทธิ์สมุดที่อยู่ไม่ทราบ"; +"contact_parse_failed" = "แยกวิเคราะห์ข้อมูลรายชื่อติดต่อล้มเหลว"; +"phone_call_initiated" = "กำลังโทรออก..."; +"phone_call_failed" = "โทรออกล้มเหลว"; +"phone_call_not_supported" = "อุปกรณ์ไม่รองรับการโทรออก"; +"invalid_phone_number" = "หมายเลขโทรศัพท์ไม่ถูกต้อง"; +"sms_app_opened" = "เปิดแอป SMS แล้ว"; +"sms_app_failed" = "เปิดแอป SMS ล้มเหลว"; +"sms_app_not_supported" = "อุปกรณ์ไม่รองรับ SMS"; +"contact" = "รายชื่อติดต่อ"; + // มุมมองตัวเลือก "picker_view_cancel" = "ยกเลิกการเลือก"; "picker_view_done" = "เสร็จสิ้นการเลือก"; @@ -825,3 +841,41 @@ "utility_info" = "ข้อมูล"; "utility_success" = "สำเร็จ"; "utility_warning" = "คำเตือน"; + + +"add_contact" = "Add Contact"; +"add_to_calendar" = "Add to Calendar"; +"app_open_failed" = "Cannot open app"; +"calendar_description" = "Description"; +"calendar_end_time" = "End Time"; +"calendar_event_add_failed" = "Failed to add event to calendar"; +"calendar_event_added_successfully" = "Event added to calendar successfully"; +"calendar_event_title" = "Event Title"; +"calendar_invalid_start_time" = "Invalid start time format"; +"calendar_location" = "Location"; +"calendar_permission_denied" = "Calendar permission required to add events"; +"calendar_permission_unknown" = "Calendar permission status unknown"; +"calendar_save_failed" = "Failed to save event"; +"calendar_start_time" = "Start Time"; +"call" = "Call"; +"connect_wifi" = "Connect WiFi"; +"copy_password" = "Copy Password"; +"email_app_failed" = "Cannot open email app"; +"message" = "Message"; +"open" = "Open"; +"open_facebook" = "Open Facebook"; +"open_instagram" = "Open Instagram"; +"open_spotify" = "Open Spotify"; +"open_viber" = "Open Viber"; +"open_whatsapp" = "Open WhatsApp"; +"open_x" = "Open X"; +"organization" = "Organization"; +"send_email" = "Send Email"; +"send_sms" = "Send SMS"; +"sms_message" = "Message"; +"sms_phone_number" = "Phone Number"; +"spotify_album" = "Album: %@"; +"spotify_artist" = "Artist: %@"; +"spotify_playlist" = "Playlist: %@"; +"spotify_track" = "Track: %@"; +"title" = "Title"; \ No newline at end of file diff --git a/MyQrCode/zh-Hans.lproj/InfoPlist.strings b/MyQrCode/zh-Hans.lproj/InfoPlist.strings index 443bdfb..7be0a79 100644 --- a/MyQrCode/zh-Hans.lproj/InfoPlist.strings +++ b/MyQrCode/zh-Hans.lproj/InfoPlist.strings @@ -7,3 +7,6 @@ "CFBundleDisplayName" = "MyQrCode"; "CFBundleName" = "MyQrCode"; +"NSContactsUsageDescription" = "此应用需要访问通讯录以添加联系人信息"; +"NSCalendarsUsageDescription" = "此应用需要访问日历来添加事件"; +"NSCalendarsWriteOnlyAccessUsageDescription" = "此应用需要只写权限来添加日历事件"; diff --git a/MyQrCode/zh-Hans.lproj/Localizable.strings b/MyQrCode/zh-Hans.lproj/Localizable.strings index c6ac00a..b366e24 100644 --- a/MyQrCode/zh-Hans.lproj/Localizable.strings +++ b/MyQrCode/zh-Hans.lproj/Localizable.strings @@ -54,6 +54,7 @@ "parsed_info" = "解析信息"; "original_content" = "原始内容"; "copy_content" = "复制内容"; +"open" = "打开"; "open_link" = "打开链接"; "decorate_code" = "装饰代码"; "qr_code_has_style" = "此二维码已有自定义样式,点击可重新编辑"; @@ -805,6 +806,70 @@ "wifi_invalid_ssid" = "无效的WiFi网络名称"; "wifi_invalid_password" = "WiFi密码格式无效"; +// 联系人相关 +"contact_added_successfully" = "联系人已成功添加到通讯录"; +"contact_add_failed" = "添加联系人失败"; +"contact_permission_denied" = "需要通讯录权限才能添加联系人"; +"contact_permission_limited" = "通讯录权限受限"; +"contact_permission_unknown" = "通讯录权限状态未知"; +"contact_parse_failed" = "解析联系人信息失败"; +"phone_call_initiated" = "正在拨打电话..."; +"phone_call_failed" = "拨打电话失败"; +"phone_call_not_supported" = "设备不支持拨打电话"; +"invalid_phone_number" = "无效的电话号码"; +"sms_app_opened" = "已打开短信应用"; +"sms_app_failed" = "打开短信应用失败"; +"sms_app_not_supported" = "设备不支持短信功能"; +"contact" = "联系人"; + +// 按钮标签 +"add_contact" = "添加联系人"; +"call" = "打电话"; +"message" = "发短信"; +"send_sms" = "发送短信"; +"add_to_calendar" = "添加到日历"; +"calendar_event_added_successfully" = "事件已成功添加到日历"; +"calendar_event_add_failed" = "添加事件到日历失败"; +"calendar_permission_denied" = "需要日历权限才能添加事件"; +"calendar_permission_unknown" = "日历权限状态未知"; +"calendar_invalid_start_time" = "开始时间格式无效"; +"calendar_save_failed" = "保存事件失败"; +"send_email" = "发送邮件"; +"email_app_failed" = "无法打开邮件应用"; +"email_subject" = "邮件主题"; +"email_body" = "邮件内容"; +"calendar_event_title" = "事件标题"; +"calendar_start_time" = "开始时间"; +"calendar_end_time" = "结束时间"; +"calendar_location" = "地点"; +"calendar_description" = "描述"; +"open_instagram" = "打开Instagram"; +"open_facebook" = "打开Facebook"; +"open_x" = "打开X"; +"open_whatsapp" = "打开WhatsApp"; +"open_viber" = "打开Viber"; +"open_spotify" = "打开Spotify"; +"app_open_failed" = "无法打开应用"; +"spotify_track" = "歌曲: %@"; +"spotify_album" = "专辑: %@"; +"spotify_artist" = "艺术家: %@"; +"spotify_playlist" = "播放列表: %@"; +"sms_phone_number" = "电话号码"; +"sms_message" = "短信内容"; +"copy_password" = "复制密码"; +"connect_wifi" = "连接WiFi"; +"open_link" = "打开链接"; +"favorite" = "收藏"; +"copy" = "复制"; + +// 联系人信息字段 +"name" = "姓名"; +"phone" = "电话"; +"email" = "邮箱"; +"organization" = "公司"; +"title" = "职位"; +"address" = "地址"; + // 选择器视图 "picker_view_cancel" = "取消选择"; "picker_view_done" = "完成选择"; diff --git a/add_missing_localizations.py b/add_missing_localizations.py deleted file mode 100644 index 7735e78..0000000 --- a/add_missing_localizations.py +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import os -import re -import glob - -def extract_keys_from_file(file_path): - """从本地化文件中提取所有key""" - keys = set() - with open(file_path, 'r', encoding='utf-8') as f: - for line in f: - match = re.match(r'^"([^"]+)"\s*=', line.strip()) - if match: - keys.add(match.group(1)) - return keys - -def get_english_translations(): - """获取英语翻译作为模板""" - translations = {} - en_file = 'MyQrCode/en.lproj/Localizable.strings' - with open(en_file, 'r', encoding='utf-8') as f: - for line in f: - match = re.match(r'^"([^"]+)"\s*=\s*"([^"]*)"\s*;', line.strip()) - if match: - key, value = match.group(1), match.group(2) - translations[key] = value - return translations - -def add_missing_keys_to_file(file_path, missing_keys, english_translations): - """为文件添加缺失的key""" - print(f"Processing {file_path}...") - - # 读取现有内容 - with open(file_path, 'r', encoding='utf-8') as f: - content = f.read() - - # 提取现有的key - existing_keys = extract_keys_from_file(file_path) - - # 找出真正缺失的key - actually_missing = missing_keys - existing_keys - - if not actually_missing: - print(f" No missing keys for {file_path}") - return - - print(f" Adding {len(actually_missing)} missing keys...") - - # 在文件末尾添加缺失的key - additions = [] - for key in sorted(actually_missing): - if key in english_translations: - # 使用英语作为默认值 - additions.append(f'"{key}" = "{english_translations[key]}";') - else: - # 如果没有英语翻译,使用key本身 - additions.append(f'"{key}" = "{key}";') - - if additions: - # 确保文件以换行符结尾 - if not content.endswith('\n'): - content += '\n' - - # 添加缺失的条目 - content += '\n// Missing keys (using English as fallback)\n' - content += '\n'.join(additions) + '\n' - - # 写回文件 - with open(file_path, 'w', encoding='utf-8') as f: - f.write(content) - - print(f" Added {len(additions)} keys to {file_path}") - -def main(): - """主函数""" - print("Adding missing localization keys...") - - # 获取英语翻译作为模板 - english_translations = get_english_translations() - print(f"Found {len(english_translations)} English translations") - - # 获取英语文件中的所有key - en_keys = extract_keys_from_file('MyQrCode/en.lproj/Localizable.strings') - print(f"Found {len(en_keys)} keys in English file") - - # 处理所有语言文件(除了英语) - lproj_files = glob.glob('MyQrCode/*.lproj/Localizable.strings') - lproj_files = [f for f in lproj_files if 'en.lproj' not in f] - - for file_path in lproj_files: - add_missing_keys_to_file(file_path, en_keys, english_translations) - - print("Done!") - -if __name__ == "__main__": - main() diff --git a/docs/CONTACT_FEATURE_ENHANCEMENT_README.md b/docs/CONTACT_FEATURE_ENHANCEMENT_README.md new file mode 100644 index 0000000..2583f8f --- /dev/null +++ b/docs/CONTACT_FEATURE_ENHANCEMENT_README.md @@ -0,0 +1,316 @@ +# VCard和MCard联系人功能增强 + +## 概述 + +为二维码详情界面添加了VCard和MCard类型的联系人功能,包括添加通讯录、打电话、发短信等功能。 + +## 新增功能 + +### 1. 联系人管理器 (ContactManager) + +**文件**: `MyQrCode/Utils/ContactManager.swift` + +#### 主要功能: +- **添加联系人到通讯录**: 解析vCard/MeCard数据并添加到系统通讯录 +- **打电话**: 直接拨打电话号码 +- **发短信**: 打开短信应用并预填电话号码 +- **联系人信息解析**: 支持vCard和MeCard格式的解析 + +#### 核心方法: +```swift +// 添加联系人 +func addContactToAddressBook(vcardContent: String, completion: @escaping (Bool, String?) -> Void) + +// 打电话 +func makePhoneCall(phoneNumber: String, completion: @escaping (Bool, String?) -> Void) + +// 发短信 +func sendSMS(phoneNumber: String, completion: @escaping (Bool, String?) -> Void) + +// 解析联系人信息 +func parseContactInfo(from content: String) -> ContactInfo? +``` + +### 2. 联系人信息结构体 + +```swift +struct ContactInfo { + var name: String = "" + var phoneNumber: String = "" + var email: String = "" + var organization: String = "" + var title: String = "" + var address: String = "" + + var displayName: String + var hasPhoneNumber: Bool + var hasEmail: Bool +} +``` + +### 3. 二维码详情界面增强 + +**文件**: `MyQrCode/Views/History/QRCodeDetailView.swift` + +#### 新增按钮: +- **添加联系人按钮**: `person.badge.plus` 图标,蓝色主题 +- **打电话按钮**: `phone` 图标,绿色主题(仅在有电话号码时显示) +- **发短信按钮**: `message` 图标,紫色主题(仅在有电话号码时显示) + +#### 条件显示逻辑: +```swift +if parsedData.type == .vcard || parsedData.type == .mecard { + // 添加联系人按钮(始终显示) + // 打电话按钮(仅在有电话号码时显示) + // 发短信按钮(仅在有电话号码时显示) +} +``` + +### 4. 权限配置 + +**文件**: `MyQrCode/Info.plist` + +添加了通讯录访问权限和URL schemes配置: +```xml +NSContactsUsageDescription +$(PRODUCT_NAME) needs access to contacts to add contact information +CFBundleURLTypes + + + CFBundleURLName + com.abe.example.demo.MyQrCode.phone + CFBundleURLSchemes + + tel + sms + + + +``` + +**本地化权限描述**: + +为所有支持的语言添加了本地化的权限描述: + +#### 中文 (zh-Hans.lproj/InfoPlist.strings) +```strings +"NSContactsUsageDescription" = "此应用需要访问通讯录以添加联系人信息"; +``` + +#### 英文 (en.lproj/InfoPlist.strings) +```strings +"NSContactsUsageDescription" = "This app needs access to contacts to add contact information"; +``` + +#### 泰语 (th.lproj/InfoPlist.strings) +```strings +"NSContactsUsageDescription" = "แอปนี้ต้องการเข้าถึงรายชื่อติดต่อเพื่อเพิ่มข้อมูลรายชื่อติดต่อ"; +``` + +#### 日语 (ja.lproj/InfoPlist.strings) +```strings +"NSContactsUsageDescription" = "このアプリは連絡先情報を追加するために連絡先へのアクセスが必要です"; +``` + +#### 韩语 (ko.lproj/InfoPlist.strings) +```strings +"NSContactsUsageDescription" = "이 앱은 연락처 정보를 추가하기 위해 연락처에 대한 액세스가 필요합니다"; +``` + +#### 法语 (fr.lproj/InfoPlist.strings) +```strings +"NSContactsUsageDescription" = "Cette application a besoin d'accéder aux contacts pour ajouter des informations de contact"; +``` + +#### 德语 (de.lproj/InfoPlist.strings) +```strings +"NSContactsUsageDescription" = "Diese App benötigt Zugriff auf Kontakte, um Kontaktinformationen hinzuzufügen"; +``` + +#### 西班牙语 (es.lproj/InfoPlist.strings) +```strings +"NSContactsUsageDescription" = "Esta aplicación necesita acceso a los contactos para agregar información de contacto"; +``` + +#### 意大利语 (it.lproj/InfoPlist.strings) +```strings +"NSContactsUsageDescription" = "Questa app ha bisogno di accedere ai contatti per aggiungere informazioni di contatto"; +``` + +#### 葡萄牙语 (pt.lproj/InfoPlist.strings) +```strings +"NSContactsUsageDescription" = "Este aplicativo precisa acessar os contatos para adicionar informações de contato"; +``` + +#### 俄语 (ru.lproj/InfoPlist.strings) +```strings +"NSContactsUsageDescription" = "Это приложение требует доступ к контактам для добавления контактной информации"; +``` + +### 5. 本地化支持 + +#### 中文 (zh-Hans.lproj/Localizable.strings) +```strings +"contact_added_successfully" = "联系人已成功添加到通讯录"; +"contact_add_failed" = "添加联系人失败"; +"contact_permission_denied" = "需要通讯录权限才能添加联系人"; +"contact_permission_limited" = "通讯录权限受限"; +"contact_permission_unknown" = "通讯录权限状态未知"; +"contact_parse_failed" = "解析联系人信息失败"; +"phone_call_initiated" = "正在拨打电话..."; +"phone_call_failed" = "拨打电话失败"; +"phone_call_not_supported" = "设备不支持拨打电话"; +"invalid_phone_number" = "无效的电话号码"; +"sms_app_opened" = "已打开短信应用"; +"sms_app_failed" = "打开短信应用失败"; +"sms_app_not_supported" = "设备不支持短信功能"; +"contact" = "联系人"; +``` + +#### 英文 (en.lproj/Localizable.strings) +```strings +"contact_added_successfully" = "Contact added to address book successfully"; +"contact_add_failed" = "Failed to add contact"; +"contact_permission_denied" = "Contact permission required to add contact"; +"contact_permission_limited" = "Contact permission limited"; +"contact_permission_unknown" = "Contact permission status unknown"; +"contact_parse_failed" = "Failed to parse contact information"; +"phone_call_initiated" = "Initiating phone call..."; +"phone_call_failed" = "Failed to make phone call"; +"phone_call_not_supported" = "Device does not support phone calls"; +"invalid_phone_number" = "Invalid phone number"; +"sms_app_opened" = "SMS app opened"; +"sms_app_failed" = "Failed to open SMS app"; +"sms_app_not_supported" = "Device does not support SMS"; +"contact" = "Contact"; +``` + +#### 泰语 (th.lproj/Localizable.strings) +```strings +"contact_added_successfully" = "เพิ่มรายชื่อติดต่อในสมุดที่อยู่สำเร็จแล้ว"; +"contact_add_failed" = "เพิ่มรายชื่อติดต่อล้มเหลว"; +"contact_permission_denied" = "ต้องการสิทธิ์สมุดที่อยู่เพื่อเพิ่มรายชื่อติดต่อ"; +"contact_permission_limited" = "สิทธิ์สมุดที่อยู่ถูกจำกัด"; +"contact_permission_unknown" = "สถานะสิทธิ์สมุดที่อยู่ไม่ทราบ"; +"contact_parse_failed" = "แยกวิเคราะห์ข้อมูลรายชื่อติดต่อล้มเหลว"; +"phone_call_initiated" = "กำลังโทรออก..."; +"phone_call_failed" = "โทรออกล้มเหลว"; +"phone_call_not_supported" = "อุปกรณ์ไม่รองรับการโทรออก"; +"invalid_phone_number" = "หมายเลขโทรศัพท์ไม่ถูกต้อง"; +"sms_app_opened" = "เปิดแอป SMS แล้ว"; +"sms_app_failed" = "เปิดแอป SMS ล้มเหลว"; +"sms_app_not_supported" = "อุปกรณ์ไม่รองรับ SMS"; +"contact" = "รายชื่อติดต่อ"; +``` + +## 技术实现细节 + +### 1. 权限处理 +- 使用 `CNContactStore.authorizationStatus(for: .contacts)` 检查权限状态 +- 支持 `.authorized`, `.denied`, `.restricted`, `.notDetermined`, `.limited` 状态 +- 自动请求权限并处理用户拒绝的情况 + +### 2. vCard/MeCard解析 +- **vCard解析**: 支持vCard 2.1和3.0格式,自动标准化为3.0版本 +- **MeCard解析**: 解析MeCard格式的联系人信息 +- 提取姓名、电话号码、邮箱、组织、职位、地址等信息 + +### 3. 联系人保存 +- 使用 `CNContactVCardSerialization` 解析vCard数据 +- 创建 `CNMutableContact` 对象 +- 使用 `CNSaveRequest` 保存到系统通讯录 + +### 4. 电话和短信功能 +- **打电话**: 使用 `tel://` URL scheme + - 自动清理电话号码格式(移除空格、括号等) + - 支持国际号码格式 + - 使用系统电话应用拨打电话 +- **发短信**: 使用 `sms://` URL scheme + - 自动清理电话号码格式 + - 支持预填短信内容 + - 使用系统短信应用发送短信 +- **URL Schemes配置**: 在Info.plist中配置了tel和sms URL schemes + +### 5. 错误处理 +- 完善的错误处理和用户反馈 +- 支持权限被拒绝、解析失败、保存失败等情况的处理 +- 提供清晰的错误信息给用户 + +## 用户体验 + +### 1. 智能按钮显示 +- 根据联系人信息智能显示相关按钮 +- 只有在有电话号码时才显示打电话和发短信按钮 +- 避免显示无用的功能按钮 + +### 2. 权限引导 +- 首次使用时自动请求通讯录权限 +- 权限被拒绝时提供清晰的提示信息 +- 引导用户到系统设置中开启权限 + +### 3. 操作反馈 +- 所有操作都有相应的成功/失败提示 +- 使用本地化字符串提供多语言支持 +- 操作结果通过Alert显示给用户 + +## 兼容性 + +- 支持iOS 15.6及以上版本 +- 兼容vCard 2.1和3.0格式 +- 支持MeCard格式 +- 适配不同权限状态(包括iOS 14+的有限权限) + +## 测试建议 + +1. **权限测试**: + - 首次使用时的权限请求 + - 权限被拒绝后的处理 + - 权限被限制时的处理 + +2. **功能测试**: + - vCard格式的解析和保存 + - MeCard格式的解析和保存 + - 打电话功能 + - 发短信功能 + +3. **错误处理测试**: + - 无效的vCard/MeCard数据 + - 网络连接问题 + - 系统通讯录访问失败 + +## 权限描述最佳实践 + +### 1. 本地化支持 +- 为所有支持的语言提供本地化的权限描述 +- 使用 `InfoPlist.strings` 文件进行权限描述的本地化 +- 确保描述内容符合各语言的文化习惯 + +### 2. 描述内容要求 +- **明确性**: 清楚说明为什么需要这个权限 +- **具体性**: 解释权限的具体用途 +- **用户友好**: 使用用户容易理解的语言 +- **简洁性**: 避免冗长的描述 + +### 3. 技术实现 +- 在 `Info.plist` 中使用 `$(PRODUCT_NAME)` 变量 +- 通过本地化文件提供各语言的权限描述 +- 确保编译时正确引用本地化字符串 + +### 4. 合规性 +- 符合 Apple 的隐私政策要求 +- 遵循 App Store 审核指南 +- 提供透明的权限使用说明 + +## 总结 + +这次功能增强为VCard和MCard类型的二维码提供了完整的联系人管理功能,包括: +- ✅ 添加联系人到通讯录 +- ✅ 直接打电话 +- ✅ 发送短信 +- ✅ 完善的权限处理 +- ✅ 多语言支持(包括权限描述) +- ✅ 智能UI显示 +- ✅ 错误处理和用户反馈 +- ✅ 符合隐私政策的最佳实践 + +所有功能都已经过编译测试,确保代码质量和稳定性。