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.

main
v504 2 months ago
parent 2d231efa2d
commit b5b73e83e9

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

@ -180,5 +180,149 @@
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "0E8D1C5E-D350-449F-8C7B-5D789F9A72B1"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MyQrCode/Views/History/QRCodeDetailView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "281"
endingLineNumber = "281"
landmarkName = "calendarInfoDetailView(parsedData:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "F08E0DA2-9528-44DD-84C4-6FFE88025996"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MyQrCode/Utils/ContactManager.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "158"
endingLineNumber = "158"
landmarkName = "displayName"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "66E3965C-B61F-41C2-8E26-A980FACCA02F"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MyQrCode/Utils/ContactManager.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "160"
endingLineNumber = "160"
landmarkName = "ContactInfo"
landmarkType = "14">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "90FE7547-4C1B-433C-95EB-40E3462AE332"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MyQrCode/Utils/ContactManager.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "162"
endingLineNumber = "162"
landmarkName = "hasPhoneNumber"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "B69F44A7-267E-4217-AD80-ABC7AA0467C3"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MyQrCode/Utils/ContactManager.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "164"
endingLineNumber = "164"
landmarkName = "ContactInfo"
landmarkType = "14">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "79BD07E2-D88D-471A-91C4-AE31376FE726"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MyQrCode/Utils/ContactManager.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "166"
endingLineNumber = "166"
landmarkName = "hasEmail"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "63CBE32C-F133-4BD4-91C9-69D7049E6733"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MyQrCode/Utils/ContactManager.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "168"
endingLineNumber = "168"
landmarkName = "ContactInfo"
landmarkType = "14">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "44E1A269-6600-475B-88AB-F6BB797B2EEE"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MyQrCode/Views/History/QRCodeDetailView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "624"
endingLineNumber = "624"
landmarkName = "actionButtonsSection"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "081EEF9C-8A5B-4C89-8DB2-0BAC1AABA1D6"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MyQrCode/Views/History/QRCodeDetailView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "1371"
endingLineNumber = "1371"
landmarkName = "openSocialApp(content:appType:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>

@ -16,10 +16,37 @@
<string>ru</string>
<string>th</string>
</array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.abe.example.demo.MyQrCode.phone</string>
<key>CFBundleURLSchemes</key>
<array>
<string>tel</string>
<string>sms</string>
</array>
</dict>
<dict>
<key>CFBundleURLName</key>
<string>com.abe.example.demo.MyQrCode.social</string>
<key>CFBundleURLSchemes</key>
<array>
<string>instagram</string>
<string>fb</string>
<string>twitter</string>
<string>whatsapp</string>
<string>viber</string>
<string>spotify</string>
</array>
</dict>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict/>
</dict>
<key>NSCalendarsWriteOnlyAccessUsageDescription</key>
<string>$(PRODUCT_NAME) needs write-only access to calendar to add events</string>
</dict>
</plist>

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

@ -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 {
// vCard3.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"
//
let formats = [
"yyyyMMdd'T'HHmmss", // 20241201T140000
"yyyyMMdd'T'HHmmss'Z'", // Z20241201T140000Z
"yyyyMMdd", // 20241201
"yyyyMMdd'T'HHmm", // 20241201T1400
"yyyy-MM-dd'T'HH:mm:ss", // ISO2024-12-01T14:00:00
"yyyy-MM-dd'T'HH:mm:ss'Z'" // ISOZ2024-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") {
// URLID
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 }

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

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

@ -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,21 @@ 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)
@ -131,12 +144,208 @@ Button("confirm".localized) { }
.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)
.font(.body)
.foregroundColor(.secondary)
.multilineTextAlignment(.leading)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
.padding()
.background(Color.green.opacity(0.1))
.cornerRadius(8)
}
}
}
// 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: -
private var originalContentSection: some View {
VStack(alignment: .leading, spacing: 12) {
@ -233,66 +442,211 @@ Button("confirm".localized) { }
.padding()
}
// 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)
// 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)
}
//
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)
.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())
}
// WiFi
// MARK: -
private var actionButtonsSection: some View {
Group {
if let content = historyItem.content {
let parsedData = QRCodeParser.parseQRCode(content)
// 使
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
)
// QR
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)
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)
// 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)
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 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)
} 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
)
Spacer()
actionButton(
icon: "doc.on.doc",
title: "copy".localized,
color: .blue,
action: copyContent
)
}
}
}
.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) {
// vCardMCard使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<Content: View>: 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
}
}
}

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

@ -7,3 +7,4 @@
"CFBundleDisplayName" = "MyQrCode";
"CFBundleName" = "MyQrCode";
"NSContactsUsageDescription" = "Diese App benötigt Zugriff auf Kontakte, um Kontaktinformationen hinzuzufügen";

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

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

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

@ -7,3 +7,4 @@
"CFBundleDisplayName" = "MyQrCode";
"CFBundleName" = "MyQrCode";
"NSContactsUsageDescription" = "Esta aplicación necesita acceso a los contactos para agregar información de contacto";

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

@ -7,3 +7,4 @@
"CFBundleDisplayName" = "MyQrCode";
"CFBundleName" = "MyQrCode";
"NSContactsUsageDescription" = "Cette application a besoin d'accéder aux contacts pour ajouter des informations de contact";

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

@ -7,3 +7,4 @@
"CFBundleDisplayName" = "MyQrCode";
"CFBundleName" = "MyQrCode";
"NSContactsUsageDescription" = "Questa app ha bisogno di accedere ai contatti per aggiungere informazioni di contatto";

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

@ -7,3 +7,4 @@
"CFBundleDisplayName" = "MyQrCode";
"CFBundleName" = "MyQrCode";
"NSContactsUsageDescription" = "このアプリは連絡先情報を追加するために連絡先へのアクセスが必要です";

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

@ -7,3 +7,4 @@
"CFBundleDisplayName" = "MyQrCode";
"CFBundleName" = "MyQrCode";
"NSContactsUsageDescription" = "이 앱은 연락처 정보를 추가하기 위해 연락처에 대한 액세스가 필요합니다";

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

@ -7,3 +7,4 @@
"CFBundleDisplayName" = "MyQrCode";
"CFBundleName" = "MyQrCode";
"NSContactsUsageDescription" = "Este aplicativo precisa acessar os contatos para adicionar informações de contato";

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

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

@ -7,3 +7,4 @@
"CFBundleDisplayName" = "MyQrCode";
"CFBundleName" = "MyQrCode";
"NSContactsUsageDescription" = "Это приложение требует доступ к контактам для добавления контактной информации";

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

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

@ -7,3 +7,4 @@
"CFBundleDisplayName" = "MyQrCode";
"CFBundleName" = "MyQrCode";
"NSContactsUsageDescription" = "แอปนี้ต้องการเข้าถึงรายชื่อติดต่อเพื่อเพิ่มข้อมูลรายชื่อติดต่อ";

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

@ -7,3 +7,6 @@
"CFBundleDisplayName" = "MyQrCode";
"CFBundleName" = "MyQrCode";
"NSContactsUsageDescription" = "此应用需要访问通讯录以添加联系人信息";
"NSCalendarsUsageDescription" = "此应用需要访问日历来添加事件";
"NSCalendarsWriteOnlyAccessUsageDescription" = "此应用需要只写权限来添加日历事件";

@ -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" = "完成选择";

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

@ -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
<key>NSContactsUsageDescription</key>
<string>$(PRODUCT_NAME) needs access to contacts to add contact information</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.abe.example.demo.MyQrCode.phone</string>
<key>CFBundleURLSchemes</key>
<array>
<string>tel</string>
<string>sms</string>
</array>
</dict>
</array>
```
**本地化权限描述**:
为所有支持的语言添加了本地化的权限描述:
#### 中文 (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显示
- ✅ 错误处理和用户反馈
- ✅ 符合隐私政策的最佳实践
所有功能都已经过编译测试,确保代码质量和稳定性。
Loading…
Cancel
Save