apply new localized string format

pull/1023/head
Ryan ZHAO 1 year ago
parent 3c7ac96b5a
commit 4054796da1

@ -24,7 +24,9 @@ final class CallMissedTipsModal: Modal {
private lazy var titleLabel: UILabel = { private lazy var titleLabel: UILabel = {
let result: UILabel = UILabel() let result: UILabel = UILabel()
result.font = .boldSystemFont(ofSize: Values.mediumFontSize) result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
result.text = "callsMissed".localized() result.text = "callsMissed"
.put(key: "name", value: caller)
.localized()
result.themeTextColor = .textPrimary result.themeTextColor = .textPrimary
result.textAlignment = .center result.textAlignment = .center
@ -34,11 +36,13 @@ final class CallMissedTipsModal: Modal {
private lazy var messageLabel: UILabel = { private lazy var messageLabel: UILabel = {
let result: UILabel = UILabel() let result: UILabel = UILabel()
result.font = .systemFont(ofSize: Values.smallFontSize) result.font = .systemFont(ofSize: Values.smallFontSize)
result.text = String(format: "callsYouMissedCallPermissions".localized(), caller)
result.themeTextColor = .textPrimary result.themeTextColor = .textPrimary
result.textAlignment = .natural result.textAlignment = .natural
result.lineBreakMode = .byWordWrapping result.lineBreakMode = .byWordWrapping
result.numberOfLines = 0 result.numberOfLines = 0
result.attributedText = "callsYouMissedCallPermissions"
.put(key: "name", value: caller)
.localizedFormatted(in: result)
return result return result
}() }()

@ -332,7 +332,7 @@ public final class SearchResultsBar: UIView {
switch results.count { switch results.count {
case 0: case 0:
// Keyboard toolbar label when no messages match the search string // Keyboard toolbar label when no messages match the search string
label.text = "searchMatchesNoneSpecific".localized() label.text = "searchMatchesNone".localized()
case 1: case 1:
// Keyboard toolbar label when exactly 1 message matches the search string // Keyboard toolbar label when exactly 1 message matches the search string
@ -343,11 +343,12 @@ public final class SearchResultsBar: UIView {
// //
// Embeds {{number/position of the 'currently viewed' result}} and // Embeds {{number/position of the 'currently viewed' result}} and
// the {{total number of results}} // the {{total number of results}}
let format = "searchMatches".localized()
guard let currentIndex: Int = currentIndex else { return } guard let currentIndex: Int = currentIndex else { return }
label.text = String(format: format, currentIndex + 1, results.count) label.text = "searchMatches"
.put(key: "count", value: currentIndex + 1)
.put(key: "totalcount", value: results.count)
.localized()
} }
if let currentIndex: Int = currentIndex { if let currentIndex: Int = currentIndex {

@ -147,10 +147,9 @@ extension ConversationVC:
self.viewModel.threadData.threadIsBlocked == true self.viewModel.threadData.threadIsBlocked == true
else { return false } else { return false }
let message = String( let message = "blockUnblockDescription"
format: "blockUnblockDescription".localized(), .put(key: "name", value: self.viewModel.threadData.displayName)
self.viewModel.threadData.displayName .localized()
)
let confirmationModal: ConfirmationModal = ConfirmationModal( let confirmationModal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info( info: ConfirmationModal.Info(
title: String( title: String(
@ -900,7 +899,7 @@ extension ConversationVC:
guard cellViewModel.variant != .infoDisappearingMessagesUpdate else { guard cellViewModel.variant != .infoDisappearingMessagesUpdate else {
let messageDisappearingConfig = cellViewModel.messageDisappearingConfiguration() let messageDisappearingConfig = cellViewModel.messageDisappearingConfiguration()
let expirationTimerString: String = floor(messageDisappearingConfig.durationSeconds).formatted(format: .long) let expirationTimerString: String = floor(messageDisappearingConfig.durationSeconds).formatted(format: .long)
let expirationTypeString: String = (messageDisappearingConfig.type == .disappearAfterRead ? "read".localized().lowercased() : "disappearingMessagesSent".localized().lowercased()) let expirationTypeString: String = (messageDisappearingConfig.type?.localizedName ?? "")
let modalBodyString: String = ( let modalBodyString: String = (
messageDisappearingConfig.isEnabled ? messageDisappearingConfig.isEnabled ?
String( String(
@ -955,16 +954,12 @@ extension ConversationVC:
// If it's an incoming media message and the thread isn't trusted then show the placeholder view // If it's an incoming media message and the thread isn't trusted then show the placeholder view
if cellViewModel.cellType != .textOnlyMessage && cellViewModel.variant == .standardIncoming && !cellViewModel.threadIsTrusted { if cellViewModel.cellType != .textOnlyMessage && cellViewModel.variant == .standardIncoming && !cellViewModel.threadIsTrusted {
let message: String = String( let message: String = "attachmentsAutoDownloadModalDescription"
format: "attachmentsAutoDownloadModalDescription".localized(), .put(key: "conversationname", value: cellViewModel.authorName)
cellViewModel.authorName .localized()
)
let confirmationModal: ConfirmationModal = ConfirmationModal( let confirmationModal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info( info: ConfirmationModal.Info(
title: String( title: "attachmentsAutoDownloadModalTitle".localized(),
format: "attachmentsAutoDownloadModalTitle".localized(),
cellViewModel.authorName
),
body: .attributedText( body: .attributedText(
NSAttributedString(string: message) NSAttributedString(string: message)
.adding( .adding(
@ -1211,7 +1206,9 @@ extension ConversationVC:
// URLs can be unsafe, so always ask the user whether they want to open one // URLs can be unsafe, so always ask the user whether they want to open one
let actionSheet: UIAlertController = UIAlertController( let actionSheet: UIAlertController = UIAlertController(
title: "urlOpen".localized(), title: "urlOpen".localized(),
message: String(format: "urlOpenDescription".localized(), url.absoluteString), message: "urlOpenDescription"
.put(key: "url", value: url.absoluteString)
.localized(),
preferredStyle: .actionSheet preferredStyle: .actionSheet
) )
actionSheet.addAction(UIAlertAction(title: "open".localized(), style: .default) { [weak self] _ in actionSheet.addAction(UIAlertAction(title: "open".localized(), style: .default) { [weak self] _ in
@ -2196,7 +2193,7 @@ extension ConversationVC:
default: default:
return (cellViewModel.threadId == userPublicKey ? return (cellViewModel.threadId == userPublicKey ?
"delete_message_for_me_and_my_devices".localized() : "delete_message_for_me_and_my_devices".localized() :
String(format: "clearMessagesForEveryone".localized(), threadName) "clearMessagesForEveryone".localized()
) )
} }
}(), }(),

@ -738,21 +738,24 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers
} }
private func emptyStateText(for threadData: SessionThreadViewModel) -> String { private func emptyStateText(for threadData: SessionThreadViewModel) -> String {
return String( switch (threadData.threadIsNoteToSelf, threadData.canWrite) {
format: { case (true, _):
switch (threadData.threadIsNoteToSelf, threadData.canWrite) { return "noteToSelfEmpty".localized()
case (true, _): return "noteToSelfEmpty".localized() case (_, false):
case (_, false): return (threadData.profile?.blocksCommunityMessageRequests == true ?
return (threadData.profile?.blocksCommunityMessageRequests == true ? String(
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE".localized() : format: "COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE".localized(),
"conversationsEmpty".localized() threadData.displayName
) ) :
"conversationsEmpty"
default: return "groupNoMessages".localized() .put(key: "conversationname", value: threadData.displayName)
} .localized()
}(), )
threadData.displayName default:
) return "groupNoMessages"
.put(key: "groupname", value: threadData.displayName)
.localized()
}
} }
private func handleThreadUpdates(_ updatedThreadData: SessionThreadViewModel, initialLoad: Bool = false) { private func handleThreadUpdates(_ updatedThreadData: SessionThreadViewModel, initialLoad: Bool = false) {

@ -120,39 +120,11 @@ final class InfoMessageCell: MessageCell {
iconImageView.themeTintColor = .textSecondary iconImageView.themeTintColor = .textSecondary
} }
if cellViewModel.variant == .infoDisappearingMessagesUpdate, let body: String = cellViewModel.body { self.label.attributedText = cellViewModel.body?.formatted(in: self.label)
self.label.attributedText = NSAttributedString(string: body)
.adding( if cellViewModel.canDoFollowingSetting() {
attributes: [ .font: UIFont.boldSystemFont(ofSize: Values.verySmallFontSize) ], self.actionLabel.isHidden = false
range: (body as NSString).range(of: cellViewModel.authorName) self.actionLabel.text = "FOLLOW_SETTING_TITLE".localized()
)
.adding(
attributes: [ .font: UIFont.boldSystemFont(ofSize: Values.verySmallFontSize) ],
range: (body as NSString).range(of: "onionRoutingPathYou".localized())
)
.adding(
attributes: [ .font: UIFont.boldSystemFont(ofSize: Values.verySmallFontSize) ],
range: (body as NSString).range(of: floor(cellViewModel.expiresInSeconds ?? 0).formatted(format: .long))
)
.adding(
attributes: [ .font: UIFont.boldSystemFont(ofSize: Values.verySmallFontSize) ],
range: (body as NSString).range(of: "read".localized().lowercased())
)
.adding(
attributes: [ .font: UIFont.boldSystemFont(ofSize: Values.verySmallFontSize) ],
range: (body as NSString).range(of: "disappearingMessagesSent".localized().lowercased())
)
.adding(
attributes: [ .font: UIFont.boldSystemFont(ofSize: Values.verySmallFontSize) ],
range: (body as NSString).range(of: "off".localized().lowercased())
)
if cellViewModel.canDoFollowingSetting() {
self.actionLabel.isHidden = false
self.actionLabel.text = "FOLLOW_SETTING_TITLE".localized()
}
} else {
self.label.text = cellViewModel.body
} }
self.label.themeTextColor = (cellViewModel.variant == .infoClosedGroupCurrentUserErrorLeaving) ? .danger : .textSecondary self.label.themeTextColor = (cellViewModel.variant == .infoClosedGroupCurrentUserErrorLeaving) ? .danger : .textSecondary

@ -381,7 +381,7 @@ class ThreadSettingsViewModel: SessionTableViewModel, NavigationItemSource, Navi
UIImage(named: "actionsheet_camera_roll_black")? UIImage(named: "actionsheet_camera_roll_black")?
.withRenderingMode(.alwaysTemplate) .withRenderingMode(.alwaysTemplate)
), ),
title: MediaStrings.allMedia, title: "conversationsSettingsAllMedia".localized(),
accessibility: Accessibility( accessibility: Accessibility(
identifier: "\(ThreadSettingsViewModel.self).all_media", identifier: "\(ThreadSettingsViewModel.self).all_media",
label: "All media" label: "All media"
@ -453,21 +453,16 @@ class ThreadSettingsViewModel: SessionTableViewModel, NavigationItemSource, Navi
return "off".localized() return "off".localized()
} }
guard Features.useNewDisappearingMessagesConfig else { guard Features.useNewDisappearingMessagesConfig else {
return String( return "disappearingMessagesDisappear"
format: "disappearingMessagesDisappear".localized(), .put(key: "disappearingmessagestype", value: "")
"", .put(key: "time", value: current.disappearingMessagesConfig.durationString)
current.disappearingMessagesConfig.durationString .localized()
)
} }
return String( return "disappearingMessagesDisappear"
format: "disappearingMessagesDisappear".localized(), .put(key: "disappearingmessagestype", value: (current.disappearingMessagesConfig.type?.localizedName ?? ""))
(current.disappearingMessagesConfig.type == .disappearAfterRead ? .put(key: "time", value: current.disappearingMessagesConfig.durationString)
"read".localized() : .localized()
"disappearingMessagesSent".localized()
),
current.disappearingMessagesConfig.durationString
)
}(), }(),
accessibility: Accessibility( accessibility: Accessibility(
identifier: "Disappearing messages", identifier: "Disappearing messages",
@ -528,21 +523,14 @@ class ThreadSettingsViewModel: SessionTableViewModel, NavigationItemSource, Navi
title: "groupLeave".localized(), title: "groupLeave".localized(),
body: .attributedText({ body: .attributedText({
if currentUserIsClosedGroupAdmin { if currentUserIsClosedGroupAdmin {
return NSAttributedString(string: "groupOnlyAdmin".localized()) return "groupOnlyAdmin"
.put(key: "groupname", value: threadViewModel.displayName)
.localizedFormatted(baseFont: .boldSystemFont(ofSize: Values.smallFontSize))
} }
let mutableAttributedString = NSMutableAttributedString( return "communityLeaveDescription"
string: String( .put(key: "communityname", value: threadViewModel.displayName)
format: "communityLeaveDescription".localized(), .localizedFormatted(baseFont: .boldSystemFont(ofSize: Values.smallFontSize))
threadViewModel.displayName
)
)
mutableAttributedString.addAttribute(
.font,
value: UIFont.boldSystemFont(ofSize: Values.smallFontSize),
range: (mutableAttributedString.string as NSString).range(of: threadViewModel.displayName)
)
return mutableAttributedString
}()), }()),
confirmTitle: "leave".localized(), confirmTitle: "leave".localized(),
confirmStyle: .danger, confirmStyle: .danger,
@ -707,7 +695,11 @@ class ThreadSettingsViewModel: SessionTableViewModel, NavigationItemSource, Navi
) )
}(), }(),
body: (threadViewModel.threadIsBlocked == true ? .none : body: (threadViewModel.threadIsBlocked == true ? .none :
.text("blockDescription".localized()) .text(
"blockDescription"
.put(key: "name", value: threadViewModel.displayName)
.localized()
)
), ),
confirmTitle: (threadViewModel.threadIsBlocked == true ? confirmTitle: (threadViewModel.threadIsBlocked == true ?
"blockUnblock".localized() : "blockUnblock".localized() :

@ -232,20 +232,22 @@ final class ConversationTitleView: UIView {
guard Features.useNewDisappearingMessagesConfig else { guard Features.useNewDisappearingMessagesConfig else {
return NSAttributedString(attachment: imageAttachment) return NSAttributedString(attachment: imageAttachment)
.appending(string: " ") .appending(string: " ")
.appending(string: String( .appending(
format: "disappearingMessagesDisappear".localized(), string: "disappearingMessagesDisappear"
"", .put(key: "disappearingmessagestype", value: "")
floor(config.durationSeconds).formatted(format: .short) .put(key: "time", value: floor(config.durationSeconds).formatted(format: .short))
)) .localized()
)
} }
return NSAttributedString(attachment: imageAttachment) return NSAttributedString(attachment: imageAttachment)
.appending(string: " ") .appending(string: " ")
.appending(string: String( .appending(
format: "disappearingMessagesDisappear".localized(), string: "disappearingMessagesDisappear"
(config.type == .disappearAfterRead ? "read".localized() : "disappearingMessagesSent".localized()), .put(key: "disappearingmessagestype", value: (config.type?.localizedName ?? ""))
floor(config.durationSeconds).formatted(format: .short) .put(key: "time", value: floor(config.durationSeconds).formatted(format: .short))
)) .localized()
)
}() }()
labelInfos.append( labelInfos.append(

@ -10,7 +10,7 @@ import SignalCoreKit
class EmptySearchResultCell: UITableViewCell { class EmptySearchResultCell: UITableViewCell {
private lazy var messageLabel: UILabel = { private lazy var messageLabel: UILabel = {
let result = UILabel() let result = UILabel()
result.text = "searchMatchesNoneSpecific".localized() result.text = "searchMatchesNone".localized()
result.themeTextColor = .textPrimary result.themeTextColor = .textPrimary
result.textAlignment = .center result.textAlignment = .center
result.numberOfLines = 3 result.numberOfLines = 3

@ -18,12 +18,12 @@ public class AllMediaViewController: UIViewController, UIPageViewControllerDataS
private lazy var tabBar: TabBar = { private lazy var tabBar: TabBar = {
let result: TabBar = TabBar( let result: TabBar = TabBar(
tabs: [ tabs: [
TabBar.Tab(title: MediaStrings.media) { [weak self] in TabBar.Tab(title: "media".localized()) { [weak self] in
guard let self = self else { return } guard let self = self else { return }
self.pageVC.setViewControllers([ self.pages[0] ], direction: .forward, animated: false, completion: nil) self.pageVC.setViewControllers([ self.pages[0] ], direction: .forward, animated: false, completion: nil)
self.updateSelectButton(updatedData: self.mediaTitleViewController.viewModel.galleryData, inBatchSelectMode: self.mediaTitleViewController.isInBatchSelectMode) self.updateSelectButton(updatedData: self.mediaTitleViewController.viewModel.galleryData, inBatchSelectMode: self.mediaTitleViewController.isInBatchSelectMode)
}, },
TabBar.Tab(title: MediaStrings.document) { [weak self] in TabBar.Tab(title: "DOCUMENT_TAB_TITLE".localized()) { [weak self] in
guard let self = self else { return } guard let self = self else { return }
self.pageVC.setViewControllers([ self.pages[1] ], direction: .forward, animated: false, completion: nil) self.pageVC.setViewControllers([ self.pages[1] ], direction: .forward, animated: false, completion: nil)
self.endSelectMode() self.endSelectMode()
@ -70,7 +70,7 @@ public class AllMediaViewController: UIViewController, UIPageViewControllerDataS
ViewControllerUtilities.setUpDefaultSessionStyle( ViewControllerUtilities.setUpDefaultSessionStyle(
for: self, for: self,
title: MediaStrings.allMedia, title: "conversationsSettingsAllMedia".localized(),
hasCustomBackButton: false hasCustomBackButton: false
) )

@ -85,7 +85,7 @@ public class DocumentTileViewController: UIViewController, UITableViewDelegate,
ViewControllerUtilities.setUpDefaultSessionStyle( ViewControllerUtilities.setUpDefaultSessionStyle(
for: self, for: self,
title: MediaStrings.document, title:"DOCUMENT_TAB_TITLE".localized(),
hasCustomBackButton: false hasCustomBackButton: false
) )

@ -137,7 +137,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
self.navigationItem.titleView = portraitHeaderView self.navigationItem.titleView = portraitHeaderView
if showAllMediaButton { if showAllMediaButton {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: MediaStrings.allMedia, style: .plain, target: self, action: #selector(didPressAllMediaButton)) self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "conversationsSettingsAllMedia".localized(), style: .plain, target: self, action: #selector(didPressAllMediaButton))
} }
// Even though bars are opaque, we want content to be layed out behind them. // Even though bars are opaque, we want content to be layed out behind them.
@ -903,8 +903,10 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
let formattedDate = dateFormatter.string(from: date) let formattedDate = dateFormatter.string(from: date)
portraitHeaderDateLabel.text = formattedDate portraitHeaderDateLabel.text = formattedDate
let landscapeHeaderFormat = "attachmentsMedia".localized() let landscapeHeaderText = "attachmentsMedia"
let landscapeHeaderText = String(format: landscapeHeaderFormat, name, formattedDate) .put(key: "name", value: name)
.put(key: "datetime", value: formattedDate)
.localized()
self.title = landscapeHeaderText self.title = landscapeHeaderText
self.navigationItem.title = landscapeHeaderText self.navigationItem.title = landscapeHeaderText
} }

@ -133,7 +133,7 @@ public class MediaTileViewController: UIViewController, UICollectionViewDataSour
ViewControllerUtilities.setUpDefaultSessionStyle( ViewControllerUtilities.setUpDefaultSessionStyle(
for: self, for: self,
title: MediaStrings.allMedia, title: "conversationsSettingsAllMedia".localized(),
hasCustomBackButton: false hasCustomBackButton: false
) )

@ -488,10 +488,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
Logger.info("Exiting because we are in the background and the database password is not accessible.") Logger.info("Exiting because we are in the background and the database password is not accessible.")
let notificationContent: UNMutableNotificationContent = UNMutableNotificationContent() let notificationContent: UNMutableNotificationContent = UNMutableNotificationContent()
notificationContent.body = String( notificationContent.body = "notificationsIosRestart"
format: "notificationsIosRestart".localized(), .put(key: "device", value: UIDevice.current.localizedModel)
UIDevice.current.localizedModel .localized()
)
let notificationRequest: UNNotificationRequest = UNNotificationRequest( let notificationRequest: UNNotificationRequest = UNNotificationRequest(
identifier: UUID().uuidString, identifier: UUID().uuidString,
content: notificationContent, content: notificationContent,

@ -686,7 +686,6 @@
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Unable to convert image."; "ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Unable to convert image.";
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Unable to process video."; "ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Unable to process video.";
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Unable to resize image."; "ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Unable to resize image.";
"GROUP_TITLE_CHANGED" = "Title is now '%@'. ";
"GROUP_CREATED" = "Group created"; "GROUP_CREATED" = "Group created";
"DISAPPERING_MESSAGES_INFO_DISABLE" = "%@ has turned off disappearing messages. Messages they send will no longer disappear."; "DISAPPERING_MESSAGES_INFO_DISABLE" = "%@ has turned off disappearing messages. Messages they send will no longer disappear.";
"DISAPPERING_MESSAGES_INFO_ENABLE" = "%@ has set messages to disappear %@ after they have been %@"; "DISAPPERING_MESSAGES_INFO_ENABLE" = "%@ has set messages to disappear %@ after they have been %@";

@ -145,16 +145,15 @@ public class NotificationPresenter: NotificationsProtocol {
notificationTitle = (isMessageRequest ? "sessionMessenger".localized() : senderName) notificationTitle = (isMessageRequest ? "sessionMessenger".localized() : senderName)
case .legacyGroup, .group, .community: case .legacyGroup, .group, .community:
notificationTitle = String( notificationTitle = "notificationsIosGroup"
format: NotificationStrings.incomingGroupMessageTitleFormat, .put(key: "name", value: senderName)
senderName, .put(key: "conversationname", value: groupName)
groupName .localized()
)
} }
} }
switch previewType { switch previewType {
case .noNameNoPreview, .nameNoPreview: notificationBody = NotificationStrings.incomingMessageBody case .noNameNoPreview, .nameNoPreview: notificationBody = "messageNewYouveGotA".localized()
case .nameAndPreview: notificationBody = messageText case .nameAndPreview: notificationBody = messageText
} }
@ -261,15 +260,13 @@ public class NotificationPresenter: NotificationsProtocol {
let notificationBody: String? = { let notificationBody: String? = {
switch messageInfo.state { switch messageInfo.state {
case .permissionDenied: case .permissionDenied:
return String( return "callsYouMissedCallPermissions"
format: "callsYouMissedCallPermissions".localized(), .put(key: "name", value: senderName)
senderName .localized()
)
case .missed: case .missed:
return String( return "callsMissedCallFrom"
format: "callsMissedCallFrom".localized(), .put(key: "name", value: senderName)
senderName .localized()
)
default: default:
return nil return nil
} }
@ -324,7 +321,7 @@ public class NotificationPresenter: NotificationsProtocol {
switch previewType { switch previewType {
case .nameAndPreview: break case .nameAndPreview: break
default: notificationBody = NotificationStrings.incomingMessageBody default: notificationBody = "messageNewYouveGotA".localized()
} }
let category = AppNotificationCategory.incomingMessage let category = AppNotificationCategory.incomingMessage
@ -390,7 +387,7 @@ public class NotificationPresenter: NotificationsProtocol {
case .nameNoPreview, .nameAndPreview: notificationTitle = threadName case .nameNoPreview, .nameAndPreview: notificationTitle = threadName
} }
let notificationBody = NotificationStrings.failedToSendBody let notificationBody = "messageErrorDelivery".localized()
let userInfo: [AnyHashable: Any] = [ let userInfo: [AnyHashable: Any] = [
AppNotificationUserInfoKey.threadId: thread.id, AppNotificationUserInfoKey.threadId: thread.id,

@ -300,7 +300,9 @@ public enum PushRegistrationError: Error {
if let messageInfoData: Data = try? JSONEncoder().encode(messageInfo) { if let messageInfoData: Data = try? JSONEncoder().encode(messageInfo) {
return String(data: messageInfoData, encoding: .utf8) return String(data: messageInfoData, encoding: .utf8)
} else { } else {
return "Incoming call." // TODO: We can do better here. return "callsIncoming"
.put(key: "name", value: caller)
.localized()
} }
}() }()

@ -33,16 +33,16 @@ class UserNotificationConfig {
case .markAsRead: case .markAsRead:
return UNNotificationAction( return UNNotificationAction(
identifier: action.identifier, identifier: action.identifier,
title: MessageStrings.markAsReadNotificationAction, title: "messageMarkRead".localized(),
options: [] options: []
) )
case .reply: case .reply:
return UNTextInputNotificationAction( return UNTextInputNotificationAction(
identifier: action.identifier, identifier: action.identifier,
title: MessageStrings.replyNotificationAction, title: "reply".localized(),
options: [], options: [],
textInputButtonTitle: MessageStrings.sendButton, textInputButtonTitle: "send".localized(),
textInputPlaceholder: "" textInputPlaceholder: ""
) )
} }
@ -153,10 +153,9 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee {
content.title : content.title :
threadName threadName
) )
content.body = String( content.body = "messageNewYouveGotMany"
format: NotificationStrings.incomingCollapsedMessagesBody, .put(key: "count", value: numberOfNotifications)
"\(numberOfNotifications)" .localized()
)
} }
content.userInfo[AppNotificationUserInfoKey.threadNotificationCounter] = numberOfNotifications content.userInfo[AppNotificationUserInfoKey.threadNotificationCounter] = numberOfNotifications

@ -181,13 +181,10 @@ public class BlockedContactsViewModel: SessionTableViewModel, NavigatableStateHo
let confirmationTitle: String = { let confirmationTitle: String = {
guard contactNames.count > 1 else { guard contactNames.count > 1 else {
// Show a single users name // Show a single users name
return String( let name: String = contactNames.first ?? "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_FALLBACK".localized()
format: "blockUnblockDescription".localized(), return "blockUnblockDescription"
( .put(key: "name", value: name)
contactNames.first ?? .localized()
"CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_FALLBACK".localized()
)
)
} }
guard contactNames.count > 3 else { guard contactNames.count > 3 else {
// Show up to three users names // Show up to three users names
@ -195,10 +192,9 @@ public class BlockedContactsViewModel: SessionTableViewModel, NavigatableStateHo
let lastName: String = contactNames[contactNames.count - 1] let lastName: String = contactNames[contactNames.count - 1]
return [ return [
String( "blockUnblockDescription"
format: "blockUnblockDescription".localized(), .put(key: "name", value: initialNames.joined(separator: ", "))
initialNames.joined(separator: ", ") .localized(),
),
String( String(
format: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_2_SINGLE".localized(), format: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_2_SINGLE".localized(),
lastName lastName
@ -214,10 +210,9 @@ public class BlockedContactsViewModel: SessionTableViewModel, NavigatableStateHo
let initialNames: [String] = Array(contactNames.prefix(upTo: numNamesToShow)) let initialNames: [String] = Array(contactNames.prefix(upTo: numNamesToShow))
return [ return [
String( "blockUnblockDescription"
format: "blockUnblockDescription".localized(), .put(key: "name", value: initialNames.joined(separator: ", "))
initialNames.joined(separator: ", ") .localized(),
),
String( String(
format: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_3".localized(), format: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_3".localized(),
(contactNames.count - numNamesToShow) (contactNames.count - numNamesToShow)

@ -228,10 +228,15 @@ final class NukeDataModal: Modal {
let message: String let message: String
if potentiallyMaliciousSnodes.count == 1 { if potentiallyMaliciousSnodes.count == 1 {
message = String(format: "clearDataErrorDescription1".localized(), potentiallyMaliciousSnodes[0]) message = "clearDataErrorDescription1"
.put(key: "servicenodeid", value: potentiallyMaliciousSnodes[0])
.localized()
} }
else { else {
message = String(format: "clearDataErrorDescription2".localized(), String(potentiallyMaliciousSnodes.count), potentiallyMaliciousSnodes.joined(separator: ", ")) message = "clearDataErrorDescription2"
.put(key: "count", value: potentiallyMaliciousSnodes.count)
.put(key: "servicenodeid", value: potentiallyMaliciousSnodes.joined(separator: ", "))
.localized()
} }
let modal: ConfirmationModal = ConfirmationModal( let modal: ConfirmationModal = ConfirmationModal(

@ -383,21 +383,14 @@ public extension UIContextualAction {
let confirmationModalExplanation: NSAttributedString = { let confirmationModalExplanation: NSAttributedString = {
if threadViewModel.currentUserIsClosedGroupAdmin == true { if threadViewModel.currentUserIsClosedGroupAdmin == true {
return NSAttributedString(string: "groupOnlyAdmin".localized()) return "groupOnlyAdmin"
.put(key: "groupname", value: threadViewModel.displayName)
.localizedFormatted(baseFont: .boldSystemFont(ofSize: Values.smallFontSize))
} }
let mutableAttributedString = NSMutableAttributedString( return "communityLeaveDescription"
string: String( .put(key: "communityname", value: threadViewModel.displayName)
format: "communityLeaveDescription".localized(), .localizedFormatted(baseFont: .boldSystemFont(ofSize: Values.smallFontSize))
threadViewModel.displayName
)
)
mutableAttributedString.addAttribute(
.font,
value: UIFont.boldSystemFont(ofSize: Values.smallFontSize),
range: (mutableAttributedString.string as NSString).range(of: threadViewModel.displayName)
)
return mutableAttributedString
}() }()
let confirmationModal: ConfirmationModal = ConfirmationModal( let confirmationModal: ConfirmationModal = ConfirmationModal(
@ -466,36 +459,27 @@ public extension UIContextualAction {
) )
} }
guard threadViewModel.currentUserIsClosedGroupAdmin == false else { guard threadViewModel.currentUserIsClosedGroupAdmin == false else {
return NSAttributedString( return "groupOnlyAdmin"
string: "groupOnlyAdmin".localized() .put(key: "groupname", value: threadViewModel.displayName)
) .localizedFormatted(baseFont: .boldSystemFont(ofSize: Values.smallFontSize))
} }
let message = String( switch threadViewModel.threadVariant {
format: { case .contact:
switch threadViewModel.threadVariant { return "conversationsDeleteDescription"
case .contact: .put(key: "name", value: threadViewModel.displayName)
return .localizedFormatted(baseFont: .boldSystemFont(ofSize: Values.smallFontSize))
"conversationsDeleteDescription".localized()
case .legacyGroup, .group:
case .legacyGroup, .group: return "groupDeleteDescription"
return .put(key: "groupname", value: threadViewModel.displayName)
"groupDeleteDescription".localized() .localizedFormatted(baseFont: .boldSystemFont(ofSize: Values.smallFontSize))
case .community: case .community:
return "communityLeaveDescription".localized() return "communityLeaveDescription"
} .put(key: "communityname", value: threadViewModel.displayName)
}(), .localizedFormatted(baseFont: .boldSystemFont(ofSize: Values.smallFontSize))
threadViewModel.displayName }
)
return NSAttributedString(string: message)
.adding(
attributes: [
.font: UIFont.boldSystemFont(ofSize: Values.smallFontSize)
],
range: (message as NSString).range(of: threadViewModel.displayName)
)
}() }()
let confirmationModal: ConfirmationModal = ConfirmationModal( let confirmationModal: ConfirmationModal = ConfirmationModal(

@ -40,6 +40,17 @@ public struct DisappearingMessagesConfiguration: Codable, Identifiable, Equatabl
case unknown case unknown
case disappearAfterRead case disappearAfterRead
case disappearAfterSend case disappearAfterSend
public var localizedName: String {
switch self {
case .unknown:
return ""
case .disappearAfterRead:
return "read".localized().lowercased()
case .disappearAfterSend:
return "disappearingMessagesSent".localized().lowercased()
}
}
init(protoType: SNProtoContent.SNProtoContentExpirationType) { init(protoType: SNProtoContent.SNProtoContentExpirationType) {
switch protoType { switch protoType {
@ -140,23 +151,21 @@ public extension DisappearingMessagesConfiguration {
return "disappearingMessagesTurnedOffYou".localized() return "disappearingMessagesTurnedOffYou".localized()
} }
return String( return "disappearingMessagesSetYou"
format: "disappearingMessagesSetYou".localized(), .put(key: "time", value: floor(durationSeconds).formatted(format: .long))
floor(durationSeconds).formatted(format: .long), .put(key: "disappearingmessagestype", value: (type?.localizedName ?? ""))
(type == .disappearAfterRead ? "read".localized().lowercased() : "disappearingMessagesSent".localized().lowercased()) .localized()
)
} }
guard isEnabled, durationSeconds > 0 else { guard isEnabled, durationSeconds > 0 else {
return String(format: "DISAPPERING_MESSAGES_INFO_DISABLE".localized(), senderName) return String(format: "DISAPPERING_MESSAGES_INFO_DISABLE".localized(), senderName)
} }
return String( return "disappearingMessagesSet"
format: "disappearingMessagesSet".localized(), .put(key: "name", value: senderName)
senderName, .put(key: "time", value: floor(durationSeconds).formatted(format: .long))
floor(durationSeconds).formatted(format: .long), .put(key: "disappearingmessagestype", value: (type?.localizedName ?? ""))
(type == .disappearAfterRead ? "read".localized().lowercased() : "disappearingMessagesSent".localized().lowercased()) .localized()
)
} }
// TODO: Remove me // TODO: Remove me
@ -165,14 +174,16 @@ public extension DisappearingMessagesConfiguration {
// Changed by this device or via synced transcript // Changed by this device or via synced transcript
guard isEnabled, durationSeconds > 0 else { return "disappearingMessagesTurnedOffYou".localized() } guard isEnabled, durationSeconds > 0 else { return "disappearingMessagesTurnedOffYou".localized() }
return String( return "disappearingMessagesSetYou"
format: "disappearingMessagesSetYou".localized(), .put(key: "time", value: floor(durationSeconds).formatted(format: .long))
floor(durationSeconds).formatted(format: .long) .put(key: "disappearingmessagestype", value: (type?.localizedName ?? ""))
) .localized()
} }
guard isEnabled, durationSeconds > 0 else { guard isEnabled, durationSeconds > 0 else {
return String(format: "disappearingMessagesTurnedOff".localized(), senderName) return "disappearingMessagesTurnedOff"
.put(key: "name", value: senderName)
.localized()
} }
return String( return String(

@ -1026,10 +1026,14 @@ public extension Interaction {
case .infoMediaSavedNotification: case .infoMediaSavedNotification:
// TODO: Use referencedAttachmentTimestamp to tell the user * which * media was saved // TODO: Use referencedAttachmentTimestamp to tell the user * which * media was saved
return String(format: "attachmentsMediaSaved".localized(), authorDisplayName) return "attachmentsMediaSaved"
.put(key: "name", value: authorDisplayName)
.localized()
case .infoScreenshotNotification: case .infoScreenshotNotification:
return String(format: "screenshotTaken".localized(), authorDisplayName) return "screenshotTaken"
.put(key: "name", value: authorDisplayName)
.localized()
case .infoClosedGroupCreated: return "GROUP_CREATED".localized() case .infoClosedGroupCreated: return "GROUP_CREATED".localized()
case .infoClosedGroupCurrentUserLeft: return "groupMemberYouLeft".localized() case .infoClosedGroupCurrentUserLeft: return "groupMemberYouLeft".localized()

@ -242,22 +242,19 @@ public extension CallMessage {
func previewText(threadContactDisplayName: String) -> String { func previewText(threadContactDisplayName: String) -> String {
switch state { switch state {
case .incoming: case .incoming:
return String( return "callsCalledYou"
format: "callsCalledYou".localized(), .put(key: "name", value: threadContactDisplayName)
threadContactDisplayName .localized()
)
case .outgoing: case .outgoing:
return String( return "callsYouCalled"
format: "callsYouCalled".localized(), .put(key: "name", value: threadContactDisplayName)
threadContactDisplayName .localized()
)
case .missed, .permissionDenied: case .missed, .permissionDenied:
return String( return "callsMissedCallFrom"
format: "callsMissedCallFrom".localized(), .put(key: "name", value: threadContactDisplayName)
threadContactDisplayName .localized()
)
// TODO: We should do better here // TODO: We should do better here
case .unknown: return "" case .unknown: return ""

@ -364,7 +364,9 @@ public extension ClosedGroupControlMessage.Kind {
func infoMessage(_ db: Database, sender: String) throws -> String? { func infoMessage(_ db: Database, sender: String) throws -> String? {
switch self { switch self {
case .nameChange(let name): case .nameChange(let name):
return String(format: "GROUP_TITLE_CHANGED".localized(), name) return "groupNameNew"
.put(key: "groupname", value: name)
.localized()
case .membersAdded(let membersAsData): case .membersAdded(let membersAsData):
let memberIds: [String] = membersAsData.map { $0.toHexString() } let memberIds: [String] = membersAsData.map { $0.toHexString() }
@ -377,10 +379,9 @@ public extension ClosedGroupControlMessage.Kind {
Profile.truncated(id: $0, threadVariant: .legacyGroup) Profile.truncated(id: $0, threadVariant: .legacyGroup)
} }
return String( return "groupMemberNew"
format: "groupMemberNew".localized(), .put(key: "name", value: addedMemberNames.joined(separator: ", "))
addedMemberNames.joined(separator: ", ") .localized()
)
case .membersRemoved(let membersAsData): case .membersRemoved(let membersAsData):
let userPublicKey: String = getUserHexEncodedPublicKey(db) let userPublicKey: String = getUserHexEncodedPublicKey(db)
@ -399,18 +400,21 @@ public extension ClosedGroupControlMessage.Kind {
knownMemberNameMap[$0] ?? knownMemberNameMap[$0] ??
Profile.truncated(id: $0, threadVariant: .legacyGroup) Profile.truncated(id: $0, threadVariant: .legacyGroup)
} }
let format: String = (removedMemberNames.count > 1 ? // TODO: Need logic change
"groupRemovedMore".localized() :
"groupRemoved".localized()
)
infoMessage = infoMessage.appending( infoMessage = infoMessage.appending(
String(format: format, removedMemberNames.joined(separator: ", ")) "groupRemoved"
.put(key: "name", value: removedMemberNames.joined(separator: ", "))
.localized()
) )
} }
// TODO: Need logic change
if memberIds.contains(userPublicKey) { if memberIds.contains(userPublicKey) {
infoMessage = infoMessage.appending("groupRemovedYou".localized()) infoMessage = infoMessage
.appending(
"groupRemovedYou"
.put(key: "groupname", value: "")
.localized()
)
} }
return infoMessage return infoMessage
@ -421,7 +425,9 @@ public extension ClosedGroupControlMessage.Kind {
guard sender != userPublicKey else { return "groupMemberYouLeft".localized() } guard sender != userPublicKey else { return "groupMemberYouLeft".localized() }
if let displayName: String = Profile.displayNameNoFallback(db, id: sender) { if let displayName: String = Profile.displayNameNoFallback(db, id: sender) {
return String(format: "groupMemberLeft".localized(), displayName) return "groupMemberLeft"
.put(key: "name", value: displayName)
.localized()
} }
return "groupUpdated".localized() return "groupUpdated".localized()

@ -514,6 +514,7 @@ public extension MessageViewModel {
} }
func canDoFollowingSetting() -> Bool { func canDoFollowingSetting() -> Bool {
guard self.variant == .infoDisappearingMessagesUpdate else { return false }
guard self.authorId != self.currentUserPublicKey else { return false } guard self.authorId != self.currentUserPublicKey else { return false }
guard self.threadVariant == .contact else { return false } guard self.threadVariant == .contact else { return false }
return self.messageDisappearingConfiguration() != self.threadDisappearingConfiguration() return self.messageDisappearingConfiguration() != self.threadDisappearingConfiguration()

@ -33,11 +33,10 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol {
return return
} }
notificationTitle = String( notificationTitle = "notificationsIosGroup"
format: NotificationStrings.incomingGroupMessageTitleFormat, .put(key: "name", value: senderName)
senderName, .put(key: "conversationname", value: groupName)
groupName .localized()
)
} }
let snippet: String = (interaction.previewText(db) let snippet: String = (interaction.previewText(db)
@ -71,11 +70,11 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol {
case .nameNoPreview: case .nameNoPreview:
notificationContent.title = notificationTitle notificationContent.title = notificationTitle
notificationContent.body = NotificationStrings.incomingMessageBody notificationContent.body = "messageNewYouveGotA".localized()
case .noNameNoPreview: case .noNameNoPreview:
notificationContent.title = "sessionMessenger".localized() notificationContent.title = "sessionMessenger".localized()
notificationContent.body = NotificationStrings.incomingMessageBody notificationContent.body = "messageNewYouveGotA".localized()
} }
// If it's a message request then overwrite the body to be something generic (only show a notification // If it's a message request then overwrite the body to be something generic (only show a notification
@ -110,10 +109,9 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol {
notificationContent.title : notificationContent.title :
groupName groupName
) )
notificationContent.body = String( notificationContent.body = "messageNewYouveGotMany"
format: NotificationStrings.incomingCollapsedMessagesBody, .put(key: "count", value: numberOfNotifications)
"\(numberOfNotifications)" .localized()
)
} }
notificationContent.userInfo[NotificationServiceExtension.threadNotificationCounter] = numberOfNotifications notificationContent.userInfo[NotificationServiceExtension.threadNotificationCounter] = numberOfNotifications
@ -167,10 +165,9 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol {
let senderName: String = Profile.displayName(db, id: interaction.authorId, threadVariant: thread.variant) let senderName: String = Profile.displayName(db, id: interaction.authorId, threadVariant: thread.variant)
if messageInfo.state == .permissionDenied { if messageInfo.state == .permissionDenied {
notificationContent.body = String( notificationContent.body = "callsYouMissedCallPermissions"
format: "callsYouMissedCallPermissions".localized(), .put(key: "name", value: senderName)
senderName .localizedDeformatted()
)
} }
addNotifcationRequest( addNotifcationRequest(
@ -202,7 +199,7 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol {
switch previewType { switch previewType {
case .nameAndPreview: break case .nameAndPreview: break
default: notificationBody = NotificationStrings.incomingMessageBody default: notificationBody = "messageNewYouveGotA".localized()
} }
let userInfo: [String: Any] = [ let userInfo: [String: Any] = [

@ -207,6 +207,14 @@ final public class LocalizationHelper: CustomStringConvertible {
public func localizedFormatted(in view: FontAccessible) -> NSAttributedString { public func localizedFormatted(in view: FontAccessible) -> NSAttributedString {
return localizedFormatted(baseFont: (view.fontValue ?? .systemFont(ofSize: 14))) return localizedFormatted(baseFont: (view.fontValue ?? .systemFont(ofSize: 14)))
} }
public func localizedFormatted(baseFont: UIFont) -> NSAttributedString {
return NSAttributedString(stringWithHTMLTags: localized(), font: baseFont)
}
public func localizedDeformatted() -> String {
return NSAttributedString(stringWithHTMLTags: localized(), font: .systemFont(ofSize: 14)).string
}
// MARK: - Internal functions // MARK: - Internal functions
@ -214,10 +222,6 @@ final public class LocalizationHelper: CustomStringConvertible {
return "{" + key + "}" return "{" + key + "}"
} }
private func localizedFormatted(baseFont: UIFont) -> NSAttributedString {
return NSAttributedString(stringWithHTMLTags: localized(), font: baseFont)
}
// MARK: - CustomStringConvertible // MARK: - CustomStringConvertible
public var description: String { public var description: String {
@ -252,11 +256,15 @@ public extension String {
return LocalizationHelper(template: self).put(key: key, value: value) return LocalizationHelper(template: self).put(key: key, value: value)
} }
// func localized() -> String { func localized() -> String {
// return LocalizationHelper(template: self).localized() return LocalizationHelper(template: self).localized()
// } }
func localizedFormatted(in view: FontAccessible) -> NSAttributedString { func localizedFormatted(in view: FontAccessible) -> NSAttributedString {
return LocalizationHelper(template: self).localizedFormatted(in: view) return LocalizationHelper(template: self).localizedFormatted(in: view)
} }
func formatted(in view: FontAccessible) -> NSAttributedString {
return NSAttributedString(stringWithHTMLTags: self, font: (view.fontValue ?? .systemFont(ofSize: 14)))
}
} }

@ -37,14 +37,6 @@ public extension String {
) )
} }
func localized() -> String {
// If the localized string matches the key provided then the localisation failed
let localizedString = NSLocalizedString(self, comment: "")
owsAssertDebug(localizedString != self, "Key \"\(self)\" is not set in Localizable.strings")
return localizedString
}
func ranges(of substring: String, options: CompareOptions = [], locale: Locale? = nil) -> [Range<Index>] { func ranges(of substring: String, options: CompareOptions = [], locale: Locale? = nil) -> [Range<Index>] {
var ranges: [Range<Index>] = [] var ranges: [Range<Index>] = []

Loading…
Cancel
Save