diff --git a/bitchat.xcodeproj/project.pbxproj b/bitchat.xcodeproj/project.pbxproj index 2e1e5860..2e764d5e 100644 --- a/bitchat.xcodeproj/project.pbxproj +++ b/bitchat.xcodeproj/project.pbxproj @@ -72,6 +72,7 @@ isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( Services/TransportConfig.swift, + Utils/Localization.swift, ); target = 57CA17A36A2532A6CFF367BB /* bitchatShareExtension */; }; diff --git a/bitchat/Services/LocationNotesManager.swift b/bitchat/Services/LocationNotesManager.swift index 30de5d33..27a6286c 100644 --- a/bitchat/Services/LocationNotesManager.swift +++ b/bitchat/Services/LocationNotesManager.swift @@ -77,17 +77,17 @@ final class LocationNotesManager: ObservableObject { private let dependencies: LocationNotesDependencies private enum Strings { - static let noRelays = NSLocalizedString( + static let noRelays = L10n.string( "location_notes.error.no_relays", comment: "Shown when no geo relays are available near the selected location" ) static func failedToSend(_ detail: String) -> String { - let format = NSLocalizedString( + L10n.string( "location_notes.error.failed_to_send", - comment: "Shown when a location note fails to send" + comment: "Shown when a location note fails to send", + detail ) - return String(format: format, detail) } } diff --git a/bitchat/Utils/Localization.swift b/bitchat/Utils/Localization.swift index d1f71111..3447790a 100644 --- a/bitchat/Utils/Localization.swift +++ b/bitchat/Utils/Localization.swift @@ -1,12 +1,21 @@ import Foundation enum L10n { - static func string(_ key: String, comment: String) -> String { - NSLocalizedString(key, comment: comment) + static func string(_ key: String, comment: String, _ args: CVarArg...) -> String { + let basic = NSLocalizedString(key, bundle: .localization, comment: comment) + if args.isEmpty { + return basic + } + return String(format: basic, locale: .current, arguments: args) } +} - static func format(_ key: String, comment: String, _ args: CVarArg...) -> String { - let format = NSLocalizedString(key, comment: comment) - return String(format: format, locale: Locale.current, arguments: args) +private extension Bundle { + static var localization: Bundle { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle.main + #endif } } diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index f149910d..26fd4889 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -2077,7 +2077,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { for (k, v) in nostrKeyMapping where v.lowercased() == hex { nostrKeyMapping.removeValue(forKey: k) } addSystemMessage( - L10n.format( + L10n.string( "system.geohash.blocked", comment: "System message shown when a user is blocked in geohash chats", displayName @@ -2088,7 +2088,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { func unblockGeohashUser(pubkeyHexLowercased: String, displayName: String) { identityManager.setNostrBlocked(pubkeyHexLowercased, isBlocked: false) addSystemMessage( - L10n.format( + L10n.string( "system.geohash.unblocked", comment: "System message shown when a user is unblocked in geohash chats", displayName @@ -2278,7 +2278,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { if unifiedPeerService.isBlocked(peerID) { let nickname = meshService.peerNickname(peerID: peerID) ?? "user" addSystemMessage( - L10n.format( + L10n.string( "system.dm.blocked_recipient", comment: "System message when attempting to message a blocked user", nickname @@ -2353,7 +2353,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { ) } addSystemMessage( - L10n.format( + L10n.string( "system.dm.unreachable", comment: "System message when a recipient is unreachable", recipientNickname ?? L10n.string("system.common.user", comment: "Fallback recipient name") @@ -2561,7 +2561,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Check if the peer is blocked if unifiedPeerService.isBlocked(peerID) { addSystemMessage( - L10n.format( + L10n.string( "system.chat.blocked", comment: "System message when starting chat fails because peer is blocked", peerNickname @@ -2574,7 +2574,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { if let peer = unifiedPeerService.getPeer(by: peerID), peer.isFavorite && !peer.theyFavoritedUs && !peer.isConnected { addSystemMessage( - L10n.format( + L10n.string( "system.chat.requires_favorite", comment: "System message when mutual favorite requirement blocks chat", peerNickname diff --git a/bitchat/Views/ContentView.swift b/bitchat/Views/ContentView.swift index 8ca1f3c5..6aa3c71b 100644 --- a/bitchat/Views/ContentView.swift +++ b/bitchat/Views/ContentView.swift @@ -1225,7 +1225,7 @@ struct ContentView: View { } .buttonStyle(.plain) .accessibilityLabel( - L10n.format( + L10n.string( "content.accessibility.toggle_bookmark", comment: "Accessibility label for toggling a geohash bookmark", ch.geohash @@ -1271,13 +1271,11 @@ struct ContentView: View { Image(systemName: "person.2.fill") .font(.system(size: headerPeerIconSize, weight: .regular)) .accessibilityLabel( - NSString.localizedStringWithFormat( - NSLocalizedString( - "content.accessibility.people_count", - comment: "Accessibility label announcing number of people in header" - ) as NSString, + L10n.string( + "content.accessibility.people_count", + comment: "Accessibility label announcing number of people in header", headerOtherPeersCount - ) as String + ) ) Text("\(headerOtherPeersCount)") .font(.system(size: headerPeerCountFontSize, weight: .regular, design: .monospaced)) @@ -1578,7 +1576,7 @@ struct ContentView: View { encryptionStatus == .noiseSecured ? textColor : Color.red) .accessibilityLabel( - L10n.format( + L10n.string( "content.accessibility.encryption_status", comment: "Accessibility label announcing encryption status", encryptionStatus.accessibilityDescription @@ -1588,7 +1586,7 @@ struct ContentView: View { } } .accessibilityLabel( - L10n.format( + L10n.string( "content.accessibility.private_chat_header", comment: "Accessibility label describing the private chat header", privatePeerNick @@ -1751,7 +1749,7 @@ struct DeliveryStatusView: View { private enum Strings { static func delivered(to nickname: String) -> String { - L10n.format( + L10n.string( "content.delivery.delivered_to", comment: "Tooltip for delivered private messages", nickname @@ -1759,7 +1757,7 @@ struct DeliveryStatusView: View { } static func read(by nickname: String) -> String { - L10n.format( + L10n.string( "content.delivery.read_by", comment: "Tooltip for read private messages", nickname @@ -1767,7 +1765,7 @@ struct DeliveryStatusView: View { } static func failed(_ reason: String) -> String { - L10n.format( + L10n.string( "content.delivery.failed", comment: "Tooltip for failed message delivery", reason @@ -1775,7 +1773,7 @@ struct DeliveryStatusView: View { } static func deliveredToMembers(_ reached: Int, _ total: Int) -> String { - L10n.format( + L10n.string( "content.delivery.delivered_members", comment: "Tooltip for partially delivered messages", reached, diff --git a/bitchat/Views/FingerprintView.swift b/bitchat/Views/FingerprintView.swift index 3aed8fb8..132f3e83 100644 --- a/bitchat/Views/FingerprintView.swift +++ b/bitchat/Views/FingerprintView.swift @@ -32,7 +32,7 @@ struct FingerprintView: View { static let notVerifiedBadge: LocalizedStringKey = "fingerprint.badge.not_verified" static let verifiedMessage: LocalizedStringKey = "fingerprint.message.verified" static func verifyHint(_ nickname: String) -> String { - L10n.format( + L10n.string( "fingerprint.message.verify_hint", comment: "Instruction to compare fingerprints with a named peer", nickname diff --git a/bitchat/Views/LocationChannelsSheet.swift b/bitchat/Views/LocationChannelsSheet.swift index 184a6341..6d7573f6 100644 --- a/bitchat/Views/LocationChannelsSheet.swift +++ b/bitchat/Views/LocationChannelsSheet.swift @@ -54,7 +54,7 @@ struct LocationChannelsSheet: View { } static func subtitlePrefix(geohash: String, coverage: String) -> String { - L10n.format( + L10n.string( "location_channels.subtitle_prefix", comment: "Subtitle prefix showing geohash and coverage", geohash, @@ -64,7 +64,7 @@ struct LocationChannelsSheet: View { static func subtitle(prefix: String, name: String?) -> String { guard let name, !name.isEmpty else { return prefix } - return L10n.format( + return L10n.string( "location_channels.subtitle_with_name", comment: "Subtitle combining prefix and resolved location name", prefix, @@ -73,11 +73,11 @@ struct LocationChannelsSheet: View { } private static func rowTitle(label: String, count: Int) -> String { - let format = NSLocalizedString( + L10n.string( "location_channels.row_title", - comment: "List row title with participant count" + comment: "List row title with participant count", + label, count ) - return NSString.localizedStringWithFormat(format as NSString, label, count) as String } } diff --git a/bitchat/Views/LocationNotesView.swift b/bitchat/Views/LocationNotesView.swift index 171a0e42..20b8e8e7 100644 --- a/bitchat/Views/LocationNotesView.swift +++ b/bitchat/Views/LocationNotesView.swift @@ -145,11 +145,11 @@ struct LocationNotesView: View { } private func headerTitle(for count: Int) -> String { - let format = NSLocalizedString( + L10n.string( "location_notes.header", - comment: "Header displaying the geohash and localized note count" + comment: "Header displaying the geohash and localized note count", + geohash, count ) - return NSString.localizedStringWithFormat(format as NSString, geohash, count) as String } private var notesContent: some View { diff --git a/bitchat/Views/VerificationViews.swift b/bitchat/Views/VerificationViews.swift index f71559f7..415bed72 100644 --- a/bitchat/Views/VerificationViews.swift +++ b/bitchat/Views/VerificationViews.swift @@ -120,7 +120,7 @@ struct QRScanView: View { static let pastePrompt: LocalizedStringKey = "verification.scan.paste_prompt" static let validate: LocalizedStringKey = "verification.scan.validate" static func requested(_ nickname: String) -> String { - L10n.format( + L10n.string( "verification.scan.status.requested", comment: "Status text when verification is requested for a nickname", nickname diff --git a/bitchatShareExtension/ShareViewController.swift b/bitchatShareExtension/ShareViewController.swift index dda0ae77..76bab000 100644 --- a/bitchatShareExtension/ShareViewController.swift +++ b/bitchatShareExtension/ShareViewController.swift @@ -16,12 +16,12 @@ final class ShareViewController: UIViewController { private static let groupID = "group.chat.bitchat" private enum Strings { - static let nothingToShare = NSLocalizedString("share.status.nothing_to_share", comment: "Shown when the share extension receives no content") - static let noShareableContent = NSLocalizedString("share.status.no_shareable_content", comment: "Shown when provided content cannot be shared") - static let sharedLinkTitleFallback = NSLocalizedString("share.fallback.shared_link_title", comment: "Fallback title when saving a shared link") - static let sharedLinkConfirmation = NSLocalizedString("share.status.shared_link", comment: "Confirmation after successfully sharing a link") - static let sharedTextConfirmation = NSLocalizedString("share.status.shared_text", comment: "Confirmation after successfully sharing text") - static let failedToEncode = NSLocalizedString("share.status.failed_to_encode", comment: "Shown when the share payload cannot be encoded") + static let nothingToShare = L10n.string("share.status.nothing_to_share", comment: "Shown when the share extension receives no content") + static let noShareableContent = L10n.string("share.status.no_shareable_content", comment: "Shown when provided content cannot be shared") + static let sharedLinkTitleFallback = L10n.string("share.fallback.shared_link_title", comment: "Fallback title when saving a shared link") + static let sharedLinkConfirmation = L10n.string("share.status.shared_link", comment: "Confirmation after successfully sharing a link") + static let sharedTextConfirmation = L10n.string("share.status.shared_text", comment: "Confirmation after successfully sharing text") + static let failedToEncode = L10n.string("share.status.failed_to_encode", comment: "Shown when the share payload cannot be encoded") } private let statusLabel: UILabel = {