Add memory safe localized string method

This commit is contained in:
Max Radermacher 2026-04-03 16:20:31 -05:00 committed by GitHub
parent 0758c26868
commit 624ee6359d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
145 changed files with 512 additions and 457 deletions

View File

@ -861,6 +861,7 @@
50EA40912E3A899F009CB839 /* RegistrationWebSocketManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EA40902E3A899F009CB839 /* RegistrationWebSocketManager.swift */; };
50EA76E52EEA1C6000DA8CD0 /* PasteboardAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EA76E42EEA1C6000DA8CD0 /* PasteboardAttachment.swift */; };
50ED28022F0EDB0B00E57C54 /* ImageQualityTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50ED28012F0EDB0B00E57C54 /* ImageQualityTest.swift */; };
50EED2DC2F61E69100D76131 /* OWSLocalizedStringTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EED2DB2F61E69100D76131 /* OWSLocalizedStringTest.swift */; };
50EF8DCA2A1885C000A00935 /* AppIconBadgeUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EF8DC92A1885C000A00935 /* AppIconBadgeUpdater.swift */; };
50EF8DCC2A189B3000A00935 /* ConversationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EF8DCB2A189B3000A00935 /* ConversationViewModel.swift */; };
50EF8DD52A1FE55D00A00935 /* SignalAccountMergeObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EF8DD42A1FE55D00A00935 /* SignalAccountMergeObserver.swift */; };
@ -5124,6 +5125,7 @@
50EA40902E3A899F009CB839 /* RegistrationWebSocketManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationWebSocketManager.swift; sourceTree = "<group>"; };
50EA76E42EEA1C6000DA8CD0 /* PasteboardAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteboardAttachment.swift; sourceTree = "<group>"; };
50ED28012F0EDB0B00E57C54 /* ImageQualityTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageQualityTest.swift; sourceTree = "<group>"; };
50EED2DB2F61E69100D76131 /* OWSLocalizedStringTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSLocalizedStringTest.swift; sourceTree = "<group>"; };
50EF8DC42A1860EF00A00935 /* BadgeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeManager.swift; sourceTree = "<group>"; };
50EF8DC92A1885C000A00935 /* AppIconBadgeUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconBadgeUpdater.swift; sourceTree = "<group>"; };
50EF8DCB2A189B3000A00935 /* ConversationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationViewModel.swift; sourceTree = "<group>"; };
@ -14362,6 +14364,7 @@
F94261E6289B1B5400460798 /* OWSErrorTest.swift */,
F97217F528DC9A5000113D9F /* OWSFileSystemTest.swift */,
F94261EE289B1B5400460798 /* OWSFormatTest.swift */,
50EED2DB2F61E69100D76131 /* OWSLocalizedStringTest.swift */,
663F94072CED62E5002C9063 /* OWSProgressTest.swift */,
4C3EF7FC2107DDEE0007EBF7 /* ParamParserTest.swift */,
F9CAC7842919B5A400EEC1DE /* PhoneNumberRegionsTest.swift */,
@ -20251,6 +20254,7 @@
F9426248289B1B5500460798 /* OWSIdentityManagerTests.swift in Sources */,
669FAE1B2B7AC919009EE2FE /* OWSLinkPreviewSerializationTest.swift in Sources */,
F9426290289B1B5600460798 /* OWSLinkPreviewTest.swift in Sources */,
50EED2DC2F61E69100D76131 /* OWSLocalizedStringTest.swift in Sources */,
508C72242C2DFCB2000811F3 /* OWSOutgoingResendResponseTest.swift in Sources */,
0436E4B02E5E2DC20011E125 /* OWSPollTest.swift in Sources */,
663F94082CED62EC002C9063 /* OWSProgressTest.swift in Sources */,

View File

@ -862,8 +862,8 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
Logger.warn("Exiting because we are in the background and the database password is not accessible.")
let notificationContent = UNMutableNotificationContent()
notificationContent.body = String(
format: OWSLocalizedString(
notificationContent.body = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT",
comment: "Lock screen notification text presented after user powers on their device without unlocking. Embeds {{device model}} (either 'iPad' or 'iPhone')",
),

View File

@ -223,8 +223,8 @@ class LoadingViewController: UIViewController {
let unitCountCompleted = Int(Float(unitCountToComplete) * progress.percentComplete)
progressView.setProgress(percentComplete, animated: true)
percentCompleteLabel.text = String(
format: OWSLocalizedString(
percentCompleteLabel.text = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"LINK_NEW_DEVICE_SYNC_PROGRESS_PERCENT",
comment: "On a progress modal indicating the percent complete the sync process is. Embeds {{ formatted percentage }}",
),

View File

@ -134,7 +134,7 @@ final class BackupEnablingManager {
)
throw ActionSheetDisplayableError(
localizedTitle: title,
localizedMessage: String(format: message, nextRetryString),
localizedMessage: String.nonPluralLocalizedStringWithFormat(message, nextRetryString),
)
} else {
throw .genericError

View File

@ -1173,8 +1173,8 @@ class BackupSettingsViewController:
let limits = try? await backupIdService.fetchBackupIDLimits(auth: .implicit(), logger: PrefixedLogger(prefix: "[Settings]")),
!limits.hasPermitsRemaining
{
let bodyText = String(
format: OWSLocalizedString(
let bodyText = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"BACKUP_SETTINGS_CREATE_NEW_KEY_LIMIT_REACHED_WARNING_SHEET_BODY",
comment: "Explanation text for a sheet warning users they've reached a rate limit for creating Recovery Key. {{ Embeds 1: the preformatted time they must wait before enabling backups, such as \"1 week\" or \"6 hours\". }}",
),
@ -1785,8 +1785,8 @@ struct BackupSettingsView: View {
VStack(alignment: .leading) {
Label {
Text(
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"BACKUP_SETTINGS_UPLOAD_PROGRESS_SUBTITLE_PAUSED_OUT_OF_STORAGE_SPACE_FORMAT",
comment: "Subtitle for a progress bar tracking uploads that are paused because the user is out of remote storage space. Embeds 1:{{ total storage space provided, e.g. 100 GB }}; 2:{{ space the user needs to free up by deleting media, e.g. 1 GB }}.",
),
@ -2079,8 +2079,8 @@ private struct BackupExportProgressView: View {
let percentComplete = (0.95 * percentExportCompleted) + (0.05 * percentUploadCompleted)
return ProgressBarState(
style: .determinate(percentComplete: percentComplete),
label: String(
format: OWSLocalizedString(
label: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"BACKUP_SETTINGS_BACKUP_EXPORT_PROGRESS_DESCRIPTION_PREPARING_BACKUP",
comment: "Description for a progress bar tracking the preparation of a Backup. Embeds 1:{{ the percentage completed preformatted as a percent, e.g. 10% }}.",
),
@ -2353,16 +2353,16 @@ private struct BackupAttachmentDownloadProgressView: View {
case .suspended:
switch backupPlan {
case .disabled, .free, .paid, .paidAsTester:
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"BACKUP_SETTINGS_DOWNLOAD_PROGRESS_SUBTITLE_SUSPENDED",
comment: "Subtitle for a view explaining that downloads are available but not running. Embeds {{ the amount available to download as a file size, e.g. 100 MB }}.",
),
latestDownloadUpdate.bytesRemaining.formatted(.owsByteCount()),
)
case .disabling, .paidExpiringSoon:
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"BACKUP_SETTINGS_DOWNLOAD_PROGRESS_SUBTITLE_SUSPENDED_PAID_SUBSCRIPTION_EXPIRING",
comment: "Subtitle for a view explaining that downloads are available but not running, and the user's paid subscription is expiring. Embeds {{ the amount available to download as a file size, e.g. 100 MB }}.",
),
@ -2370,8 +2370,8 @@ private struct BackupAttachmentDownloadProgressView: View {
)
}
case .running:
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"BACKUP_SETTINGS_DOWNLOAD_PROGRESS_SUBTITLE_RUNNING",
comment: "Subtitle for a progress bar tracking active downloading. Embeds 1:{{ the amount downloaded as a file size, e.g. 100 MB }}; 2:{{ the total amount to download as a file size, e.g. 1 GB }}; 3:{{ the amount downloaded as a percentage, e.g. 10% }}.",
),
@ -2400,8 +2400,8 @@ private struct BackupAttachmentDownloadProgressView: View {
comment: "Subtitle for a progress bar tracking downloads that are paused because they need internet.",
)
case .outOfDiskSpace(let bytesRequired):
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"BACKUP_SETTINGS_DOWNLOAD_PROGRESS_SUBTITLE_PAUSED_NEEDS_DISK_SPACE",
comment: "Subtitle for a progress bar tracking downloads that are paused because they need more disk space available. Embeds {{ the amount of space needed as a file size, e.g. 100 MB }}.",
),
@ -2525,8 +2525,8 @@ private struct BackupAttachmentUploadProgressView: View {
let totalBytesToUpload = uploadUpdate.totalBytesToUpload
let percentageUploaded = uploadUpdate.percentageUploaded
return String(
format: OWSLocalizedString(
return String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"BACKUP_SETTINGS_UPLOAD_PROGRESS_SUBTITLE_RUNNING",
comment: "Subtitle for a progress bar tracking active uploading. Embeds 1:{{ the amount uploaded as a file size, e.g. 100 MB }}; 2:{{ the total amount to upload as a file size, e.g. 1 GB }}; 3:{{ the percentage uploaded as a percent, e.g. 40% }}.",
),
@ -2733,8 +2733,8 @@ private struct BackupSubscriptionLoadedView: View {
"BACKUP_SETTINGS_BACKUP_PLAN_PAID_PRICE_FORMAT",
comment: "Text explaining the price of the user's paid backup plan. Embeds {{ the formatted price }}.",
)
Text(String(
format: priceStringFormat,
Text(String.nonPluralLocalizedStringWithFormat(
priceStringFormat,
CurrencyFormatter.format(money: price),
))
@ -2742,8 +2742,8 @@ private struct BackupSubscriptionLoadedView: View {
"BACKUP_SETTINGS_BACKUP_PLAN_PAID_RENEWAL_FORMAT",
comment: "Text explaining when the user's paid backup plan renews. Embeds {{ the formatted renewal date }}.",
)
Text(String(
format: renewalStringFormat,
Text(String.nonPluralLocalizedStringWithFormat(
renewalStringFormat,
DateFormatter.localizedString(from: renewalDate, dateStyle: .medium, timeStyle: .none),
))
case .paidButExpiring(let expirationDate), .paidButExpired(let expirationDate):
@ -2770,8 +2770,8 @@ private struct BackupSubscriptionLoadedView: View {
.fontWeight(.semibold)
.foregroundStyle(Color.Signal.red)
Text(String(
format: expirationDateFormatString,
Text(String.nonPluralLocalizedStringWithFormat(
expirationDateFormatString,
DateFormatter.localizedString(from: expirationDate, dateStyle: .medium, timeStyle: .none),
))
case .paidButFailedToRenew:
@ -2930,21 +2930,21 @@ private struct BackupDetailsView: View {
comment: "Text explaining that the user's last backup was today. Embeds {{ the time of the backup }}.",
)
return String(format: todayFormatString, lastBackupTimeString)
return String.nonPluralLocalizedStringWithFormat(todayFormatString, lastBackupTimeString)
} else if Calendar.current.isDateInYesterday(lastBackupDate) {
let yesterdayFormatString = OWSLocalizedString(
"BACKUP_SETTINGS_ENABLED_LAST_BACKUP_YESTERDAY_FORMAT",
comment: "Text explaining that the user's last backup was yesterday. Embeds {{ the time of the backup }}.",
)
return String(format: yesterdayFormatString, lastBackupTimeString)
return String.nonPluralLocalizedStringWithFormat(yesterdayFormatString, lastBackupTimeString)
} else {
let pastFormatString = OWSLocalizedString(
"BACKUP_SETTINGS_ENABLED_LAST_BACKUP_PAST_FORMAT",
comment: "Text explaining that the user's last backup was in the past. Embeds 1:{{ the date of the backup }} and 2:{{ the time of the backup }}.",
)
return String(format: pastFormatString, lastBackupDateString, lastBackupTimeString)
return String.nonPluralLocalizedStringWithFormat(pastFormatString, lastBackupDateString, lastBackupTimeString)
}
}()

View File

@ -273,8 +273,8 @@ struct ChooseBackupPlanView: View {
title: {
switch viewModel.storeKitAvailability {
case .available(let paidPlanDisplayPrice):
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"CHOOSE_BACKUP_PLAN_PAID_PLAN_TITLE",
comment: "Title for the paid plan option, when choosing a Backup plan. Embeds {{ the formatted monthly cost, as currency, of the paid plan }}.",
),
@ -300,8 +300,8 @@ struct ChooseBackupPlanView: View {
"CHOOSE_BACKUP_PLAN_BULLET_FULL_MEDIA_BACKUP",
comment: "Text for a bullet point in a list of Backup features, describing that all media is included.",
)),
PlanOptionView.BulletPoint(iconKey: "data", text: String(
format: OWSLocalizedString(
PlanOptionView.BulletPoint(iconKey: "data", text: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"CHOOSE_BACKUP_PLAN_BULLET_STORAGE_AMOUNT",
comment: "Text for a bullet point in a list of Backup features, describing the amount of included storage. Embeds {{ the amount of storage preformatted as a localized byte count, e.g. '100 GB' }}.",
),
@ -339,8 +339,8 @@ struct ChooseBackupPlanView: View {
case .paid:
switch viewModel.storeKitAvailability {
case .available(let paidPlanDisplayPrice):
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"CHOOSE_BACKUP_PLAN_SUBSCRIBE_PAID_BUTTON_TEXT",
comment: "Text for a button that will subscribe the user to the paid Backup plan. Embeds {{ the formatted monthly cost, as currency, of the paid plan }}.",
),

View File

@ -273,12 +273,12 @@ class CallDrawerSheet: InteractiveSheetViewController, UITableViewDelegate, Call
label.attributedText = .composed(of: [
titleText.styled(with: .font(.dynamicTypeHeadline)),
" ",
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"GROUP_CALL_MEMBER_LIST_SECTION_HEADER_MEMBER_COUNT",
comment: "A count of members in a given group call member list section, displayed after the header.",
),
self.memberCount,
String(self.memberCount),
),
]).styled(
with: .font(.dynamicTypeBody),

View File

@ -194,9 +194,9 @@ class CallHeader: UIView {
case 0:
return zeroMemberString()
case 1:
return String(format: oneMemberFormat(), names[0])
return String.nonPluralLocalizedStringWithFormat(oneMemberFormat(), names[0])
case 2:
return String(format: twoMemberFormat(), names[0], names[1])
return String.nonPluralLocalizedStringWithFormat(twoMemberFormat(), names[0], names[1])
default:
return String.localizedStringWithFormat(manyMemberFormat(), count - 2, names[0], names[1])
}
@ -289,7 +289,7 @@ class CallHeader: UIView {
"GROUP_CALL_INCOMING_RING_FORMAT",
comment: "Text explaining that someone has sent a ring to the group. Embeds {ring sender name}",
)
return String(format: formatString, callerName)
return String.nonPluralLocalizedStringWithFormat(formatString, callerName)
}
private func whoIsHereText(joinedMembers: [UUID?]) -> String {
@ -316,14 +316,14 @@ class CallHeader: UIView {
"GROUP_CALL_ONE_PERSON_HERE_FORMAT",
comment: "Text explaining that there is one person in the group call. Embeds {member name}",
)
return String(format: format, someMember)
return String.nonPluralLocalizedStringWithFormat(format, someMember)
case ((let someMember, let someOtherMember?)?, 0):
// exactly two members, both known
let format = OWSLocalizedString(
"GROUP_CALL_TWO_PEOPLE_HERE_FORMAT",
comment: "Text explaining that there are two people in the group call. Embeds {{ %1$@ participant1, %2$@ participant2 }}",
)
return String(format: format, someMember, someOtherMember)
return String.nonPluralLocalizedStringWithFormat(format, someMember, someOtherMember)
case ((let someMember, let someOtherMember?)?, let otherCount):
// two or more members, at least two known
let format = OWSLocalizedString(
@ -459,7 +459,7 @@ class CallHeader: UIView {
"GROUP_CALL_PRESENTING_FORMAT",
comment: "Text explaining that a member is presenting. Embeds {member name}",
)
return String(format: formatString, presentingName)
return String.nonPluralLocalizedStringWithFormat(formatString, presentingName)
}
switch groupCall.concreteType {
case .groupThread(let groupThreadCall):

View File

@ -136,7 +136,7 @@ class CallMemberWaitingAndErrorView: UIView, CallMemberComposableView {
comment: "String displayed in group call grid cell when a user is blocked. Embeds {user's name}",
)
let displayName = displayName(address: addr)
label = String(format: blockFormat, arguments: [displayName])
label = String.nonPluralLocalizedStringWithFormat(blockFormat, displayName)
image = UIImage(named: "block")
case .noMediaKeys(let addr):
let missingKeyFormat = OWSLocalizedString(
@ -144,7 +144,7 @@ class CallMemberWaitingAndErrorView: UIView, CallMemberComposableView {
comment: "String displayed in cell when media from a user can't be displayed in group call grid. Embeds {user's name}",
)
let displayName = displayName(address: addr)
label = String(format: missingKeyFormat, arguments: [displayName])
label = String.nonPluralLocalizedStringWithFormat(missingKeyFormat, displayName)
image = UIImage(named: "error-circle-fill")
}
@ -177,7 +177,7 @@ class CallMemberWaitingAndErrorView: UIView, CallMemberComposableView {
comment: "Title for alert explaining that a group call participant is blocked. Embeds {{ user's name }}",
)
let displayName = SSKEnvironment.shared.databaseStorageRef.read { tx in SSKEnvironment.shared.contactManagerRef.displayName(for: address, tx: tx).resolvedValue() }
title = String(format: titleFormat, displayName)
title = String.nonPluralLocalizedStringWithFormat(titleFormat, displayName)
case let .noMediaKeys(address):
message = OWSLocalizedString(
@ -190,7 +190,7 @@ class CallMemberWaitingAndErrorView: UIView, CallMemberComposableView {
comment: "Title for alert explaining that a group call participant cannot be displayed because of missing keys. Embeds {{ user's name }}",
)
let displayName = SSKEnvironment.shared.databaseStorageRef.read { tx in SSKEnvironment.shared.contactManagerRef.displayName(for: address, tx: tx).resolvedValue() }
title = String(format: titleFormat, displayName)
title = String.nonPluralLocalizedStringWithFormat(titleFormat, displayName)
}
return (title, message)

View File

@ -1630,9 +1630,9 @@ class CallsListViewController: OWSViewController, HomeTabViewController, CallSer
private func updateEmptyStateMessage() {
switch (viewModelLoader.isEmpty, searchTerm) {
case (true, .some(let searchTerm)) where !searchTerm.isEmpty:
noSearchResultsView.text = String(
format: Strings.searchNoResultsFoundLabelFormat,
arguments: [searchTerm],
noSearchResultsView.text = String.nonPluralLocalizedStringWithFormat(
Strings.searchNoResultsFoundLabelFormat,
searchTerm,
)
noSearchResultsView.alpha = 1
emptyStateMessageView.alpha = 0
@ -2498,7 +2498,7 @@ private extension CallsListViewController {
if viewModel.callRecords.count <= 1 {
return viewModel.title
} else {
return String(format: Strings.coalescedCallsTitleFormat, viewModel.title, "\(viewModel.callRecords.count)")
return String.nonPluralLocalizedStringWithFormat(Strings.coalescedCallsTitleFormat, viewModel.title, "\(viewModel.callRecords.count)")
}
}()
titleLabel.text = titleText

View File

@ -200,7 +200,7 @@ private class BannerView: UIView {
"GROUP_CALL_NOTIFICATION_TWO_LEFT_FORMAT",
comment: "Copy explaining that two users have left the group call. Embeds {first member name}, {second member name}",
)
actionText = String(format: formatText, displayNames[0], displayNames[1])
actionText = String.nonPluralLocalizedStringWithFormat(formatText, displayNames[0], displayNames[1])
} else {
let formatText = action == .join
? OWSLocalizedString(
@ -211,7 +211,7 @@ private class BannerView: UIView {
"GROUP_CALL_NOTIFICATION_ONE_LEFT_FORMAT",
comment: "Copy explaining that a user has left the group call. Embeds {member name}",
)
actionText = String(format: formatText, displayNames[0])
actionText = String.nonPluralLocalizedStringWithFormat(formatText, displayNames[0])
}
let hStack = UIStackView()

View File

@ -182,8 +182,8 @@ enum GroupCallVideoContextMenuConfiguration {
ringRtcGroupCall: SignalRingRTC.GroupCall,
) {
let actionSheet = ActionSheetController(
title: String(
format: OWSLocalizedString(
title: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"GROUP_CALL_REMOVE_MEMBER_CONFIRMATION_ACTION_SHEET_TITLE",
comment: "Title for action sheet confirming removal of a member from a group call. embeds {{ name }}",
),

View File

@ -1378,8 +1378,8 @@ private class PermissionErrorView: UIView {
}
let needPermissionLabel = UILabel()
needPermissionLabel.text = String(
format: OWSLocalizedString(
needPermissionLabel.text = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"CALL_VIEW_NEED_PERMISSION_ERROR_FORMAT",
comment: "Error displayed on the 'call' view when the callee needs to grant permission before we can call them. Embeds {callee short name}.",
),

View File

@ -239,12 +239,12 @@ class RaisedHandsToast: UIView {
}
self.collapsedText = if self.yourHandIsRaised, raisedHands.count > 1 {
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"RAISED_HANDS_TOAST_YOU_PLUS_OTHERS_COUNT",
comment: "A compact member count on the call view's raised hands toast indicating that you and a number of other users raised a hand. Embeds {{number of other users}}",
),
raisedHands.count - 1,
String(raisedHands.count - 1),
)
} else if self.yourHandIsRaised {
CommonStrings.you
@ -285,8 +285,8 @@ class RaisedHandsToast: UIView {
return String.localizedStringWithFormat(format, otherMembersCount, firstRaisedHandMemberName)
}
return String(
format: OWSLocalizedString(
return String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"RAISED_HANDS_TOAST_SINGLE_HAND_MESSAGE",
comment: "A message appearing on the call view's raised hands toast indicating that another named member has raised their hand.",
),

View File

@ -55,15 +55,15 @@ class RemoteMuteToast: UIView {
let toastText: String
let localAci = self.deps.tsAccountManager.localIdentifiersWithMaybeSneakyTransaction!.aci
if muteSource == localAci {
toastText = String(
format: OWSLocalizedString(
toastText = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"REMOTE_MUTE_TOAST_YOU_MUTED_YOURSELF",
comment: "A message that displays when you joined a call on two devices and mute one from the other.",
),
)
} else {
toastText = String(
format: OWSLocalizedString(
toastText = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"REMOTE_MUTE_TOAST_SOMEONE_MUTED_YOU",
comment:
"A message that displays when your microphone is remotely muted by another call participant. Embeds {{name}}",
@ -91,8 +91,8 @@ class RemoteMuteToast: UIView {
).resolvedValue(useShortNameIfAvailable: true)
}
toastText = String(
format: OWSLocalizedString(
toastText = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"REMOTE_MUTE_TOAST_YOU_MUTED_SOMEONE",
comment:
"A message that displays when you remotely muted another call participant's microphone. Embeds {{name}}",
@ -117,8 +117,8 @@ class RemoteMuteToast: UIView {
)
}
toastText = String(
format: OWSLocalizedString(
toastText = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"REMOTE_MUTE_TOAST_A_MUTED_B",
comment:
"A message that displays when one person in a call remotely muted another participant. Embeds {{name}} {{name}}",

View File

@ -37,7 +37,7 @@ class BlockingAnnouncementOnlyView: ConversationBottomPanelView {
"GROUPS_ANNOUNCEMENT_ONLY_ADMINISTRATORS",
comment: "Label for group administrators in the 'announcement-only' group UI.",
)
let text = String(format: format, adminsText)
let text = String.nonPluralLocalizedStringWithFormat(format, adminsText)
let attributedString = NSMutableAttributedString(string: text)
attributedString.setAttributes([.foregroundColor: UIColor.Signal.link], forSubstring: adminsText)

View File

@ -129,8 +129,8 @@ class CVMediaAlbumView: ManualStackViewWithLayer {
let moreCount = max(1, items.count - Constants.maxItemCount)
let moreCountText = OWSFormat.formatInt(moreCount)
let moreText = String(
format: OWSLocalizedString(
let moreText = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"MEDIA_GALLERY_MORE_ITEMS_FORMAT",
comment: "Format for the 'more items' indicator for media galleries. Embeds {{the number of additional items}}.",
),

View File

@ -262,7 +262,7 @@ public class CVQuotedMessageView: ManualStackViewWithLayer {
"QUOTED_REPLY_STORY_AUTHOR_INDICATOR_FORMAT",
comment: "Message header when you are quoting a story. Embeds {{ story author name }}",
)
text = String(format: format, authorName)
text = String.nonPluralLocalizedStringWithFormat(format, authorName)
} else {
text = authorName
}
@ -415,7 +415,7 @@ public class CVQuotedMessageView: ManualStackViewWithLayer {
"QUOTED_REPLY_REACTION_TO_STORY_FORMAT_SECOND_PERSON",
comment: "Label explaining that the content of a quoted message includes you reacting to someone's story. Embeds {{ %1$@ the story author }}.",
)
text = String(format: formatText, quotedAuthorName.string)
text = String.nonPluralLocalizedStringWithFormat(formatText, quotedAuthorName.string)
}
return CVLabelConfig.unstyledText(

View File

@ -75,7 +75,7 @@ class GiftBadgeView: ManualStackView {
comment: "You sent a donation to a friend. This is the title of that message in the chat. Embeds {{short contact name}}.",
)
}
let text = String(format: textFormat, state.otherUserShortName)
let text = String.nonPluralLocalizedStringWithFormat(textFormat, state.otherUserShortName)
let textColor = state.conversationStyle.bubbleTextColor(isIncoming: state.isIncoming)

View File

@ -200,26 +200,26 @@ public class CVComponentArchivedPayment: CVComponentBase, CVComponent {
"PAYMENTS_PAYMENT_STATUS_IN_CHAT_SENT_YOU",
comment: "Payment status context with contact name, incoming. Embeds {{ Name of sending contact }}",
)
text = String(format: format, archivedPaymentAttachment.otherUserShortName)
text = String.nonPluralLocalizedStringWithFormat(format, archivedPaymentAttachment.otherUserShortName)
case (.outgoingMessage, .failed):
let format = OWSLocalizedString(
"PAYMENTS_PAYMENT_STATUS_IN_CHAT_PAYMENT_TO",
comment: "Payment status context with contact name, failed. Embeds {{ Name of receiving contact }}",
)
text = String(format: format, archivedPaymentAttachment.otherUserShortName)
text = String.nonPluralLocalizedStringWithFormat(format, archivedPaymentAttachment.otherUserShortName)
case (.outgoingMessage, _):
let format = OWSLocalizedString(
"PAYMENTS_PAYMENT_STATUS_IN_CHAT_YOU_SENT",
comment: "Payment status context with contact name, sent. Embeds {{ Name of receiving contact }}",
)
text = String(format: format, archivedPaymentAttachment.otherUserShortName)
text = String.nonPluralLocalizedStringWithFormat(format, archivedPaymentAttachment.otherUserShortName)
default:
// default to failed text because it doesn't imply success
let format = OWSLocalizedString(
"PAYMENTS_PAYMENT_STATUS_IN_CHAT_PAYMENT_TO",
comment: "Payment status context with contact name, failed. Embeds {{ Name of receiving contact }}",
)
text = String(format: format, archivedPaymentAttachment.otherUserShortName)
text = String.nonPluralLocalizedStringWithFormat(format, archivedPaymentAttachment.otherUserShortName)
}
return CVLabelConfig(

View File

@ -566,7 +566,7 @@ public class CVComponentBodyText: CVComponentBase, CVComponent {
)
text.append(
NSAttributedString(
string: String(format: format, displayName),
string: String.nonPluralLocalizedStringWithFormat(format, displayName),
),
)

View File

@ -97,7 +97,7 @@ extension CVComponentContactShare: CVAccessibilityComponent {
"ACCESSIBILITY_LABEL_CONTACT_FORMAT",
comment: "Accessibility label for contact. Embeds: {{ the contact name }}.",
)
return String(format: format, contactName)
return String.nonPluralLocalizedStringWithFormat(format, contactName)
} else {
return OWSLocalizedString(
"ACCESSIBILITY_LABEL_CONTACT",

View File

@ -1420,7 +1420,7 @@ public class CVComponentMessage: CVComponentBase, CVRootComponent {
"CONVERSATION_VIEW_CELL_ACCESSIBILITY_SENDER_FORMAT",
comment: "Format for sender info for accessibility label for message. Embeds {{ the sender name }}.",
)
elements.append(String(format: format, accessibilityAuthorName))
elements.append(String.nonPluralLocalizedStringWithFormat(format, accessibilityAuthorName))
} else {
owsFailDebug("Missing accessibilityAuthorName.")
}

View File

@ -222,26 +222,26 @@ public class CVComponentPaymentAttachment: CVComponentBase, CVComponent {
"PAYMENTS_PAYMENT_STATUS_IN_CHAT_SENT_YOU",
comment: "Payment status context with contact name, incoming. Embeds {{ Name of sending contact }}",
)
text = String(format: format, contactName)
text = String.nonPluralLocalizedStringWithFormat(format, contactName)
case (_, .outgoingMessage, .failed):
let format = OWSLocalizedString(
"PAYMENTS_PAYMENT_STATUS_IN_CHAT_PAYMENT_TO",
comment: "Payment status context with contact name, failed. Embeds {{ Name of receiving contact }}",
)
text = String(format: format, contactName)
text = String.nonPluralLocalizedStringWithFormat(format, contactName)
case (_, .outgoingMessage, _):
let format = OWSLocalizedString(
"PAYMENTS_PAYMENT_STATUS_IN_CHAT_YOU_SENT",
comment: "Payment status context with contact name, sent. Embeds {{ Name of receiving contact }}",
)
text = String(format: format, contactName)
text = String.nonPluralLocalizedStringWithFormat(format, contactName)
default:
// default to failed text because it doesn't imply success
let format = OWSLocalizedString(
"PAYMENTS_PAYMENT_STATUS_IN_CHAT_PAYMENT_TO",
comment: "Payment status context with contact name, failed. Embeds {{ Name of receiving contact }}",
)
text = String(format: format, contactName)
text = String.nonPluralLocalizedStringWithFormat(format, contactName)
}
return CVLabelConfig(

View File

@ -64,7 +64,7 @@ public class CVComponentPoll: CVComponentBase, CVComponent {
"CONVERSATION_VIEW_CELL_ACCESSIBILITY_SENDER_FORMAT",
comment: "Format for sender info for accessibility label for message. Embeds {{ the sender name }}.",
)
elements.append(String(format: senderFormat, accessibilityAuthorName))
elements.append(String.nonPluralLocalizedStringWithFormat(senderFormat, accessibilityAuthorName))
} else {
owsFailDebug("Missing accessibilityAuthorName.")
}
@ -79,7 +79,7 @@ public class CVComponentPoll: CVComponentBase, CVComponent {
"POLL_ACCESSIBILITY_LABEL",
comment: "Accessibility label for poll message. Embeds {{ poll question }}.",
)
elements.append(String(format: formatQuestion, poll.state.poll.question))
elements.append(String.nonPluralLocalizedStringWithFormat(formatQuestion, poll.state.poll.question))
let result = elements.joined(separator: " ")
return result

View File

@ -121,10 +121,10 @@ extension CVComponentQuotedReply: CVAccessibilityComponent {
let originalMessage = self.quotedReply.viewState.quotedReplyModel.originalMessageAccessibilityLabel ?? ""
if quotedReply.quotedReplyModel.isOriginalMessageAuthorLocalUser {
return String(format: format, CommonStrings.you, originalMessage)
return String.nonPluralLocalizedStringWithFormat(format, CommonStrings.you, originalMessage)
} else {
return String(
format: format,
return String.nonPluralLocalizedStringWithFormat(
format,
self.quotedReply.viewState.quotedAuthorName,
originalMessage,
)

View File

@ -238,8 +238,8 @@ public class CVComponentSticker: CVComponentBase, CVComponent {
extension CVComponentSticker: CVAccessibilityComponent {
public var accessibilityDescription: String {
if let approximateEmoji = stickerMetadata?.firstEmoji {
return String(
format: OWSLocalizedString(
return String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"ACCESSIBILITY_LABEL_STICKER_FORMAT",
comment: "Accessibility label for stickers. Embeds {{ name of top emoji the sticker resembles }}",
),

View File

@ -827,7 +827,7 @@ extension CVComponentSystemMessage {
}
let displayName = SSKEnvironment.shared.contactManagerRef.displayName(for: verificationMessage.recipientAddress, tx: transaction).resolvedValue()
return String(format: format, displayName)
return String.nonPluralLocalizedStringWithFormat(format, displayName)
}
if let infoMessage = interaction as? TSInfoMessage {
return infoMessage.conversationSystemMessageComponentText(with: transaction)
@ -1147,7 +1147,7 @@ extension CVComponentSystemMessage {
"SYSTEM_MESSAGE_DEFAULT_DISAPPEARING_MESSAGE_TIMER_FORMAT",
comment: "Indicator that the default disappearing message timer will be applied when you send a message. Embeds {default disappearing message time}",
)
labelText.append(String(format: titleFormat, configuration.durationString()))
labelText.append(String.nonPluralLocalizedStringWithFormat(titleFormat, configuration.durationString()))
return buildComponentState(title: labelText, action: nil, expiration: nil)
}

View File

@ -463,7 +463,7 @@ public class CVComponentThreadDetails: CVComponentBase, CVRootComponent {
)
}
let formattedString = String(format: predicate, subject)
let formattedString = String.nonPluralLocalizedStringWithFormat(predicate, subject)
let subjectRange = NSString(string: formattedString).range(of: subject)
let attributedString = NSMutableAttributedString(string: formattedString)
attributedString.addAttributes(
@ -987,7 +987,7 @@ extension CVComponentThreadDetails {
let formatString: String
var underlinedPortion: String?
var arguments: [CVarArg] = sortedMemberNames
var arguments: [String] = sortedMemberNames
switch (sortedMemberNames.count, localUserIsAMember) {
case (0, _):
formatString = OWSLocalizedString(
@ -1050,8 +1050,8 @@ extension CVComponentThreadDetails {
arguments = firstThreeMembers + [otherMembersString]
}
let membersString = String(
format: formatString,
let membersString = String.nonPluralLocalizedStringWithFormat(
formatString,
arguments: arguments,
)
let membersAttributedString: NSAttributedString
@ -1136,41 +1136,40 @@ extension CVComponentThreadDetails {
tx: tx,
)
// We need these to be CVarArgs for them to format appropriately.
let groupNamesFormatArg: [CVarArg] = mutualGroupNames
let groupNamesFormatArg: [String] = mutualGroupNames
let formattedString: String
switch mutualGroupNames.count {
case 0:
formattedString = String(
format: OWSLocalizedString(
formattedString = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"THREAD_DETAILS_ZERO_MUTUAL_GROUPS",
comment: "A string indicating there are no mutual groups the user shares with this contact",
),
groupNamesFormatArg,
arguments: groupNamesFormatArg,
)
case 1:
formattedString = String(
format: OWSLocalizedString(
formattedString = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"THREAD_DETAILS_ONE_MUTUAL_GROUP",
comment: "A string indicating a mutual group the user shares with this contact. Embeds {{mutual group name}}",
),
groupNamesFormatArg,
arguments: groupNamesFormatArg,
)
case 2:
formattedString = String(
format: OWSLocalizedString(
formattedString = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"THREAD_DETAILS_TWO_MUTUAL_GROUP",
comment: "A string indicating two mutual groups the user shares with this contact. Embeds {{mutual group name}}",
),
groupNamesFormatArg,
arguments: groupNamesFormatArg,
)
case 3:
formattedString = String(
format: OWSLocalizedString(
formattedString = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"THREAD_DETAILS_THREE_MUTUAL_GROUP",
comment: "A string indicating three mutual groups the user shares with this contact. Embeds {{mutual group name}}",
),
groupNamesFormatArg,
arguments: groupNamesFormatArg,
)
default:
// For this string, we want to use the first two groups' names

View File

@ -191,8 +191,8 @@ private class QuotedMessageSnippetView: UIView {
tx: tx,
).resolvedValue()
}
quotedAuthor = String(
format: NSLocalizedString(
quotedAuthor = String.nonPluralLocalizedStringWithFormat(
NSLocalizedString(
"QUOTED_REPLY_AUTHOR_INDICATOR_FORMAT",
comment: "Indicates the author of a quoted message. Embeds {{the author's name or phone number}}.",
),

View File

@ -1115,7 +1115,7 @@ private extension ConversationViewController {
"MESSAGES_VIEW_CONTACT_NO_LONGER_VERIFIED_FORMAT",
comment: "Indicates that this 1:1 conversation is no longer verified. Embeds {{user's name or phone number}}.",
)
title = String(format: format, displayName)
title = String.nonPluralLocalizedStringWithFormat(format, displayName)
default:
title = OWSLocalizedString(

View File

@ -246,7 +246,7 @@ public extension ConversationViewController {
)
let learnMoreText = CommonStrings.learnMore
let attributedString = NSMutableAttributedString(string: String(format: format, learnMoreText))
let attributedString = NSMutableAttributedString(string: String.nonPluralLocalizedStringWithFormat(format, learnMoreText))
attributedString.setAttributes(
[.foregroundColor: UIColor.Signal.link],
forSubstring: learnMoreText,
@ -263,7 +263,7 @@ public extension ConversationViewController {
"APP_EXPIRED_BOTTOM_UPDATE",
comment: "Shown in place of the text input box in a conversation when the app has expired and the user is no longer allowed to send messages. This value is a tappable link embedded in a larger sentence.",
)
let attributedString = NSMutableAttributedString(string: String(format: format, updateNowText))
let attributedString = NSMutableAttributedString(string: String.nonPluralLocalizedStringWithFormat(format, updateNowText))
attributedString.setAttributes(
[.foregroundColor: UIColor.Signal.link],
forSubstring: updateNowText,
@ -280,7 +280,7 @@ public extension ConversationViewController {
"NOT_REGISTERED_BOTTOM_REREGISTER",
comment: "Shown in place of the text input box in a conversation when the user is no longer registered can't send messages. This value is a tappable link embedded in a larger sentence.",
)
let attributedString = NSMutableAttributedString(string: String(format: format, updateNowText))
let attributedString = NSMutableAttributedString(string: String.nonPluralLocalizedStringWithFormat(format, updateNowText))
attributedString.setAttributes(
[.foregroundColor: UIColor.Signal.link],
forSubstring: updateNowText,
@ -297,7 +297,7 @@ public extension ConversationViewController {
"NOT_LINKED_BOTTOM_RELINK",
comment: "Shown in place of the text input box in a conversation when the user is no longer registered can't send messages. This value is a tappable link embedded in a larger sentence.",
)
let attributedString = NSMutableAttributedString(string: String(format: format, updateNowText))
let attributedString = NSMutableAttributedString(string: String.nonPluralLocalizedStringWithFormat(format, updateNowText))
attributedString.setAttributes(
[.foregroundColor: UIColor.Signal.link],
forSubstring: updateNowText,

View File

@ -774,7 +774,7 @@ extension ConversationViewController: CVComponentDelegate {
let actionSheet = ActionSheetController(
title: nil,
message: String(format: messageFormat, displayName),
message: String.nonPluralLocalizedStringWithFormat(messageFormat, displayName),
)
actionSheet.customHeader = headerView
@ -802,8 +802,8 @@ extension ConversationViewController: CVComponentDelegate {
let threadName = SSKEnvironment.shared.databaseStorageRef.read { transaction in
SSKEnvironment.shared.contactManagerRef.displayName(for: self.thread, transaction: transaction)
}
let alertMessage = String(
format: OWSLocalizedString(
let alertMessage = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"CORRUPTED_SESSION_DESCRIPTION",
comment: "ActionSheet title",
),
@ -895,8 +895,8 @@ extension ConversationViewController: CVComponentDelegate {
let alert = ActionSheetController(
title: CallStrings.callBackAlertTitle,
message: String(
format: CallStrings.callBackAlertMessageFormat,
message: String.nonPluralLocalizedStringWithFormat(
CallStrings.callBackAlertMessageFormat,
displayName,
),
)
@ -931,8 +931,8 @@ extension ConversationViewController: CVComponentDelegate {
let displayName = SSKEnvironment.shared.databaseStorageRef.read { tx in SSKEnvironment.shared.contactManagerRef.displayName(for: address, tx: tx).resolvedValue() }
let alert = ActionSheetController(
title: String(
format: OWSLocalizedString(
title: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"MISSED_CALL_BLOCKED_SYSTEM_SETTINGS_SHEET_TITLE",
comment: "Title for sheet shown when the user taps a missed call from a contact blocked in iOS settings. Embeds {{ Contact's name }}",
),
@ -1069,8 +1069,8 @@ extension ConversationViewController: CVComponentDelegate {
"GROUPS_BLOCK_REQUEST_SHEET_TITLE",
comment: "Title for sheet asking if the user wants to block a request to join the group.",
),
message: String(
format: OWSLocalizedString(
message: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"GROUPS_BLOCK_REQUEST_SHEET_MESSAGE",
comment: "Message for sheet offering to let the user block a request to join the group. Embeds {{ the requester's name }}.",
),
@ -1230,7 +1230,7 @@ extension ConversationViewController: CVComponentDelegate {
let shortDisplayName = SSKEnvironment.shared.databaseStorageRef.read { tx in
return SSKEnvironment.shared.contactManagerRef.displayName(for: contactAddress, tx: tx).resolvedValue(useShortNameIfAvailable: true)
}
return String(format: formatString, formattedPhoneNumber, shortDisplayName)
return String.nonPluralLocalizedStringWithFormat(formatString, formattedPhoneNumber, shortDisplayName)
}()
let customHeader: UIView = {
let imageView = UIImageView(image: UIImage(named: "merged-chat")!)
@ -1276,8 +1276,8 @@ extension ConversationViewController: CVComponentDelegate {
let message: String
if thread is TSContactThread {
message = String(
format: OWSLocalizedString(
message = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"INFO_MESSAGE_ACCEPTED_MESSAGE_REQUEST_OPTIONS_ACTION_SHEET_HEADER_CONTACT",
comment: "Header for an action sheet providing options in response to an accepted 1:1 message request. Embeds {{ the name of your chat partner }}.",
),
@ -1296,8 +1296,8 @@ extension ConversationViewController: CVComponentDelegate {
message: message,
)
alert.addAction(ActionSheetAction(
title: String(
format: OWSLocalizedString(
title: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"MESSAGE_REQUEST_ACCEPTED_INFO_MESSAGE_SHEET_OPTION_BLOCK",
comment: "Sheet option for blocking a chat. In this case, the sheet appears when the user taps a button attached to a 'message request accepted' info message in-chat.",
),
@ -1311,8 +1311,8 @@ extension ConversationViewController: CVComponentDelegate {
},
))
alert.addAction(ActionSheetAction(
title: String(
format: OWSLocalizedString(
title: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"MESSAGE_REQUEST_ACCEPTED_INFO_MESSAGE_SHEET_OPTION_SPAM",
comment: "Sheet option for reporting a chat as spam. In this case, the sheet appears when the user taps a button attached to a 'message request accepted' info message in-chat.",
),
@ -1326,8 +1326,8 @@ extension ConversationViewController: CVComponentDelegate {
},
))
alert.addAction(ActionSheetAction(
title: String(
format: OWSLocalizedString(
title: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"MESSAGE_REQUEST_ACCEPTED_INFO_MESSAGE_SHEET_OPTION_DELETE",
comment: "Sheet option for deleting a chat. In this case, the sheet appears when the user taps a button attached to a 'message request accepted' info message in-chat.",
),

View File

@ -70,8 +70,8 @@ extension ConversationViewController: MessageRequestDelegate {
return
}
let title = String(
format: OWSLocalizedString(
let title = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"BLOCK_LIST_UNBLOCK_TITLE_FORMAT",
comment: "A format for the 'unblock conversation' action sheet title. Embeds the {{conversation title}}.",
),
@ -375,8 +375,8 @@ extension ConversationViewController {
}
actionSheet.addAction(ActionSheetAction(
title: String(
format: OWSLocalizedString(
title: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"GROUPS_INVITE_BLOCK_INVITER_FORMAT",
comment: "Label for 'block inviter' button in group invite view. Embeds {{name of user who invited you}}.",
),
@ -388,8 +388,8 @@ extension ConversationViewController {
})
actionSheet.addAction(ActionSheetAction(
title: String(
format: OWSLocalizedString(
title: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"GROUPS_INVITE_BLOCK_GROUP_AND_INVITER_FORMAT",
comment: "Label for 'block group and inviter' button in group invite view. Embeds {{name of user who invited you}}.",
),
@ -437,7 +437,7 @@ extension ConversationViewController {
let hasReportedSpam = finder.hasUserReportedSpam(transaction: tx)
return (threadName, hasReportedSpam)
}
let actionSheetTitle = String(format: actionSheetTitleFormat, threadName)
let actionSheetTitle = String.nonPluralLocalizedStringWithFormat(actionSheetTitleFormat, threadName)
let actionSheet = ActionSheetController(title: actionSheetTitle, message: actionSheetMessage)
let blockActionTitle = OWSLocalizedString(

View File

@ -190,7 +190,7 @@ extension ConversationViewController {
alertMessageFormat = OWSLocalizedString("ALERT_DELIVERY_ISSUE_UNKNOWN_THREAD_MESSAGE_FORMAT", comment: "Format string for delivery issue sheet message where the original thread is unknown. Embeds {{ sender name }}.")
}
let alertMessage = String(format: alertMessageFormat, senderName)
let alertMessage = String.nonPluralLocalizedStringWithFormat(alertMessageFormat, senderName)
let headerImageView = UIImageView(image: .init(named: "delivery-issue"))
headerImageView.autoSetDimension(.height, toSize: 110)

View File

@ -139,8 +139,8 @@ class EmojiCountCell: UICollectionViewCell {
if item.emoji != nil {
count.text = item.count.abbreviatedString
} else {
count.text = String(
format: OWSLocalizedString(
count.text = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"REACTION_DETAIL_ALL_FORMAT",
comment: "The header used to indicate All reactions to a given message. Embeds {{number of reactions}}",
),

View File

@ -27,8 +27,8 @@ class BackupsEnabledNotificationMegaphone: MegaphoneView {
comment: "Title for system notification or megaphone when backups is enabled",
)
bodyText = String(
format: OWSLocalizedString(
bodyText = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"BACKUPS_TURNED_ON_NOTIFICATION_BODY_FORMAT",
comment: "Body for system notification or megaphone when backups is enabled. Embeds {{ time backups was enabled }}",
),

View File

@ -26,8 +26,8 @@ final class NewLinkedDeviceNotificationMegaphone: MegaphoneView {
comment: "Title for system notification when a new device is linked.",
)
let bodyText = String(
format: OWSLocalizedString(
let bodyText = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"LINKED_DEVICE_NOTIFICATION_MEGAPHONE_BODY",
comment: "Body for megaphone notification when a new device is linked. Embeds {{ time the device was linked }}",
),

View File

@ -208,8 +208,8 @@ struct LinkAndSyncProvisioningProgressView: View {
comment: "Progress label when the message loading has not yet started during the device linking process",
)
} else if let downloadProgress = viewModel.downloadProgress {
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"LINK_NEW_DEVICE_SYNC_DOWNLOAD_PROGRESS",
comment: "Progress label showing the download progress of a linked device sync. Embeds {{ formatted downloaded size (such as megabytes), formatted total download size, formatted percentage }}",
),
@ -218,8 +218,8 @@ struct LinkAndSyncProvisioningProgressView: View {
progressToShow.formatted(.owsPercent()),
)
} else if !viewModel.isFinalizing {
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"LINK_NEW_DEVICE_SYNC_PROGRESS_PERCENT",
comment: "On a progress modal indicating the percent complete the sync process is. Embeds {{ formatted percentage }}",
),

View File

@ -92,8 +92,8 @@ struct OutgoingDeviceRestoreBackupPromptView: View {
private func lastBackupDetailsString() -> String {
let date = lastBackupDetails.date
return String(
format: OWSLocalizedString(
return String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"OUTGOING_DEVICE_RESTORE_BACKUP_RESTORE_DESCRIPTION",
comment: "Description for form confirming restore from backup without size detail.",
),

View File

@ -54,8 +54,8 @@ class RegistrationChangePhoneNumberConfirmationViewController: OWSViewController
)
let oldPhoneNumberFormatted = PhoneNumber.bestEffortLocalizedPhoneNumber(e164: state.oldE164.stringValue)
let newPhoneNumberFormatted = PhoneNumber.bestEffortLocalizedPhoneNumber(e164: state.newE164.stringValue)
let descriptionText = String(
format: descriptionFormat,
let descriptionText = String.nonPluralLocalizedStringWithFormat(
descriptionFormat,
oldPhoneNumberFormatted,
newPhoneNumberFormatted,
)

View File

@ -115,7 +115,7 @@ class RegistrationChangePhoneNumberViewController: OWSTableViewController2 {
"SETTINGS_CHANGE_PHONE_NUMBER_COUNTRY_CODE_FORMAT",
comment: "Format for the 'country code' in the 'change phone number' settings. Embeds: {{ %1$@ the numeric country code prefix, %2$@ the country code abbreviation }}.",
)
let countryCodeFormatted = String(format: countryCodeFormat, valueViews.plusPrefixedCallingCode, valueViews.countryCode)
let countryCodeFormatted = String.nonPluralLocalizedStringWithFormat(countryCodeFormat, valueViews.plusPrefixedCallingCode, valueViews.countryCode)
section.add(.item(
name: OWSLocalizedString(
"SETTINGS_CHANGE_PHONE_NUMBER_COUNTRY_CODE_FIELD",

View File

@ -24,7 +24,7 @@ class RegistrationLoadingViewController: OWSViewController, OWSNavigationChildCo
"REGISTRATION_VIEW_PHONE_NUMBER_SPINNER_LABEL_FORMAT",
comment: "Label for the progress spinner shown during phone number registration. Embeds {{phone number}}.",
)
return String(format: format, e164.e164FormattedAsPhoneNumberWithoutBreaks)
return String.nonPluralLocalizedStringWithFormat(format, e164.e164FormattedAsPhoneNumberWithoutBreaks)
case .submittingVerificationCode:
return OWSLocalizedString(
"ONBOARDING_VERIFICATION_CODE_VALIDATION_PROGRESS_LABEL",

View File

@ -94,7 +94,7 @@ class RegistrationPhoneNumberDiscoverabilityViewController: OWSViewController {
comment: "Explanation of the 'onboarding phone number discoverability' view. Embeds {user phone number}",
)
let subtitleLabel = UILabel.explanationLabelForRegistration(
text: String(format: explanationTextFormat, formattedPhoneNumber),
text: String.nonPluralLocalizedStringWithFormat(explanationTextFormat, formattedPhoneNumber),
)
subtitleLabel.accessibilityIdentifier = "registration.phoneNumberDiscoverability.explanationLabel"

View File

@ -136,6 +136,6 @@ extension RegistrationPhoneNumberViewState.ValidationError.RateLimited {
}()
let durationString = retryAfterFormatter.string(from: Date(timeIntervalSinceReferenceDate: timeRemaining))
return String(format: rateLimitFormat, durationString)
return String.nonPluralLocalizedStringWithFormat(rateLimitFormat, durationString)
}
}

View File

@ -133,8 +133,8 @@ class RegistrationReglockTimeoutViewController: OWSViewController {
return UInt32(result <= oneMinute ? oneMinute : result)
}()
return String(
format: format,
return String.nonPluralLocalizedStringWithFormat(
format,
DateUtil.formatDuration(seconds: remainingSeconds, useShortFormat: false),
)
}

View File

@ -206,7 +206,7 @@ struct RegistrationRestoreFromBackupConfirmationView: View {
let formattedDate = DateUtil.dateFormatter.string(for: date),
let formattedTime = DateUtil.timeFormatter.string(for: date)
{
formattedString = String(format: formattedString, formattedDate, formattedTime)
formattedString = String.nonPluralLocalizedStringWithFormat(formattedString, formattedDate, formattedTime)
return Text(formattedString)
} else {
return Text("")
@ -222,7 +222,7 @@ struct RegistrationRestoreFromBackupConfirmationView: View {
let formattedDate = DateUtil.dateFormatter.string(for: date),
let formattedTime = DateUtil.timeFormatter.string(for: date)
{
formattedString = String(format: formattedString, formattedDate, formattedTime, OWSFormat.formatFileSize(size))
formattedString = String.nonPluralLocalizedStringWithFormat(formattedString, formattedDate, formattedTime, OWSFormat.formatFileSize(size))
return Text(formattedString)
} else {
return Text("")

View File

@ -136,7 +136,7 @@ class RegistrationVerificationViewController: OWSViewController {
"ONBOARDING_VERIFICATION_TITLE_DEFAULT_FORMAT",
comment: "Format for the title of the 'onboarding verification' view. Embeds {{the user's phone number}}.",
)
return String(format: format, state.e164.stringValue.e164FormattedAsPhoneNumberWithoutBreaks)
return String.nonPluralLocalizedStringWithFormat(format, state.e164.stringValue.e164FormattedAsPhoneNumberWithoutBreaks)
}
private lazy var explanationLabel: UILabel = {
@ -385,7 +385,7 @@ class RegistrationVerificationViewController: OWSViewController {
button.configuration?.title = {
let timeRemaining = max(date.timeIntervalSince(now), 0)
let durationString = retryAfterFormatter.string(from: Date(timeIntervalSinceReferenceDate: timeRemaining))
return String(format: countdownFormat, durationString)
return String.nonPluralLocalizedStringWithFormat(countdownFormat, durationString)
}()
}
}
@ -513,7 +513,7 @@ class RegistrationVerificationViewController: OWSViewController {
let timeRemaining = max(nextVerificationAttemptDate.timeIntervalSince(now), 0)
let durationString = formatter.string(from: Date(timeIntervalSinceReferenceDate: timeRemaining))
let message = String(format: format, durationString)
let message = String.nonPluralLocalizedStringWithFormat(format, durationString)
OWSActionSheets.showActionSheet(title: nil, message: message)
}
}

View File

@ -253,7 +253,7 @@ extension ActionSheetController {
"REGISTRATION_VIEW_PHONE_NUMBER_CONFIRMATION_ALERT_TITLE_FORMAT",
comment: "Title for confirmation alert during phone number registration. Embeds {{phone number}}.",
)
return String(format: format, e164.e164FormattedAsPhoneNumberWithoutBreaks)
return String.nonPluralLocalizedStringWithFormat(format, e164.e164FormattedAsPhoneNumberWithoutBreaks)
}(),
message: message,
)

View File

@ -230,7 +230,7 @@ class DeleteAccountConfirmationViewController: OWSTableViewController2 {
"SETTINGS_DELETE_ACCOUNT_PAYMENTS_BALANCE_ALERT_MESSAGE_FORMAT",
comment: "Body for the alert confirming whether the user wants transfer their payments balance before deleting their account. Embeds: {{ the current payment balance }}.",
)
let message = String(format: messageFormat, formattedBalance)
let message = String.nonPluralLocalizedStringWithFormat(messageFormat, formattedBalance)
let actionSheet = ActionSheetController(title: title, message: message)

View File

@ -225,8 +225,8 @@ final class ComposeSupportEmailOperation: NSObject {
"SUPPORT_EMAIL_INFO_DIVIDER",
comment: "Localized divider for support request emails internal information",
),
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"SUPPORT_EMAIL_FILTER_LABEL_FORMAT",
comment: "Localized label for support request email filter string. Embeds {{filter text}}.",
),
@ -236,22 +236,22 @@ final class ComposeSupportEmailOperation: NSObject {
format: "Challenge Received: %@",
model.hasRecentChallenge ? "yes" : "no",
),
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"SUPPORT_EMAIL_HARDWARE_LABEL_FORMAT",
comment: "Localized label for support request email hardware string (e.g. iPhone or iPad). Embeds {{hardware text}}.",
),
model.deviceType,
),
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"SUPPORT_EMAIL_HID_LABEL_FORMAT",
comment: "Localized label for support request email HID string (e.g. 12,1). Embeds {{hid text}}.",
),
model.deviceIdentifier,
),
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"SUPPORT_EMAIL_IOS_VERSION_LABEL_FORMAT",
comment: "Localized label for support request email iOS Version string (e.g. 13.4). Embeds {{ios version}}.",
),
@ -260,8 +260,8 @@ final class ComposeSupportEmailOperation: NSObject {
"Signal Version: \(model.signalAppVersion)",
{
if let debugURLString = model.resolvedDebugString {
return String(
format: OWSLocalizedString(
return String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"SUPPORT_EMAIL_LOG_URL_LABEL_FORMAT",
comment: "Localized label for support request email debug log URL. Embeds {{debug log url}}.",
),
@ -269,8 +269,8 @@ final class ComposeSupportEmailOperation: NSObject {
)
} else { return nil }
}(),
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"SUPPORT_EMAIL_LOCALE_LABEL_FORMAT",
comment: "Localized label for support request email locale string. Embeds {{locale}}.",
),

View File

@ -263,7 +263,7 @@ class BadgeGiftingConfirmationViewController: OWSTableViewController2 {
let durationString = String.formatDurationLossless(
durationSeconds: disappearingMessagesDuration,
)
disappearingMessagesInfoLabel.text = String(format: format, durationString)
disappearingMessagesInfoLabel.text = String.nonPluralLocalizedStringWithFormat(format, durationString)
cell.contentView.addSubview(disappearingMessagesInfoLabel)
disappearingMessagesInfoLabel.autoPinEdgesToSuperviewMargins()

View File

@ -258,7 +258,7 @@ extension DonationSettingsViewController {
let currencyString = CurrencyFormatter.format(money: amount)
return String(format: pricingFormat, currencyString)
return String.nonPluralLocalizedStringWithFormat(pricingFormat, currencyString)
}()
let statusSubtitle: String = {
@ -277,7 +277,7 @@ extension DonationSettingsViewController {
dateFormatter.dateStyle = .medium
let dateArg = dateFormatter.string(from: subscription.endOfCurrentPeriod)
return String(format: renewalFormat, dateArg)
return String.nonPluralLocalizedStringWithFormat(renewalFormat, dateArg)
case .pendingAuthorization:
owsFailDebug("Should not be displaying a subscription message for a donation pending authorization.")
return OWSLocalizedString(
@ -375,8 +375,8 @@ extension DonationSettingsViewController {
comment: "A string describing the amount and currency of a one-time payment. Embeds {{ the amount, formatted as a currency }}.",
)
return String(
format: pricingFormat,
return String.nonPluralLocalizedStringWithFormat(
pricingFormat,
CurrencyFormatter.format(money: amount),
)
}()
@ -496,7 +496,7 @@ extension DonationSettingsViewController {
paymentMethod: paymentMethod,
)
return String(format: messageFormat, chargeFailureString)
return String.nonPluralLocalizedStringWithFormat(messageFormat, chargeFailureString)
}()
let actionSheet = ActionSheetController(
@ -533,7 +533,7 @@ extension DonationSettingsViewController {
paymentMethod: paymentMethod,
)
return String(format: messageFormat, chargeFailureString)
return String.nonPluralLocalizedStringWithFormat(messageFormat, chargeFailureString)
}()
let actionSheet = ActionSheetController(

View File

@ -556,7 +556,7 @@ class DonationSettingsViewController: OWSTableViewController2 {
"DONATION_SETTINGS_MY_SUPPORT_IDEAL_ONE_TIME_DONATION_NOT_CONFIRMED_MESSAGE_FORMAT",
comment: "Title for a sheet explaining that a payment needs confirmation.",
)
let message = String(format: messageFormat, CurrencyFormatter.format(money: pendingOneTime.amount))
let message = String.nonPluralLocalizedStringWithFormat(messageFormat, CurrencyFormatter.format(money: pendingOneTime.amount))
showError(title: title, message: message, donationMode: .oneTime)
}
return true
@ -583,7 +583,7 @@ class DonationSettingsViewController: OWSTableViewController2 {
"DONATION_SETTINGS_MY_SUPPORT_IDEAL_RECURRING_SUBSCRIPTION_NOT_CONFIRMED_MESSAGE_FORMAT",
comment: "Message shown in a sheet explaining that the user's iDEAL recurring monthly donation hasn't been confirmed. Embeds {{ formatted current amount }}.",
)
let message = String(format: messageFormat, CurrencyFormatter.format(money: pendingSubscription.amount))
let message = String.nonPluralLocalizedStringWithFormat(messageFormat, CurrencyFormatter.format(money: pendingSubscription.amount))
showError(title: title, message: message, donationMode: .monthly)
}
return true

View File

@ -240,8 +240,8 @@ struct BackupRestoreProgressView: View {
comment: "Subtitle for a progress spinner on a modal when waiting for a backup restore to start",
)
} else if let downloadProgress = viewModel.downloadProgress {
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"BACKUP_RESTORE_MODAL_DOWNLOAD_PROGRESS_SUBTITLE",
comment: "Subtitle for a progress spinner on a modal tracking active downloading. Embeds 1:{{ the amount downloaded as a file size, e.g. 100 MB }}; 2:{{ the total amount to download as a file size, e.g. 1 GB }}; 3:{{ the amount downloaded as a percentage, e.g. 10% }}.",
),
@ -256,8 +256,8 @@ struct BackupRestoreProgressView: View {
}
private var percentCompleteString: String {
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"LINK_NEW_DEVICE_SYNC_PROGRESS_PERCENT",
comment: "On a progress modal indicating the percent complete the sync process is. Embeds {{ formatted percentage }}",
),

View File

@ -56,8 +56,8 @@ class LinkedDevicesEducationSheet: StackSheetViewController {
let desktopDownloadLinkString = "signal.org/download"
let desktopDownloadURL = URL(string: "https://signal.org/download/")!
let downloadsString = String(
format: OWSLocalizedString(
let downloadsString = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"LINKED_DEVICES_EDUCATION_POINT_DOWNLOADS",
comment: "Bullet point about downloads on the linked devices education sheet. Embeds {{ %1$@ iPad download link, %2$@ desktop download link }}",
),

View File

@ -491,7 +491,7 @@ class LinkedDevicesHostingController: HostingContainer<LinkedDevicesView> {
)
}
presentToast(text: String(format: title, deviceName))
presentToast(text: String.nonPluralLocalizedStringWithFormat(title, deviceName))
}
private func showUpdateFailureAlert(error: Error) {
@ -710,7 +710,7 @@ class LinkedDevicesHostingController: HostingContainer<LinkedDevicesView> {
"UNLINK_CONFIRMATION_ALERT_TITLE",
comment: "Alert title for confirming device deletion",
)
let title = String(format: titleFormat, displayableDevice.displayName)
let title = String.nonPluralLocalizedStringWithFormat(titleFormat, displayableDevice.displayName)
let message = OWSLocalizedString(
"UNLINK_CONFIRMATION_ALERT_BODY",
comment: "Alert message to confirm unlinking a device",
@ -890,8 +890,8 @@ struct LinkedDevicesView: View {
}
private func dateFormattedString(format: String, date: Date) -> String {
String(
format: format,
String.nonPluralLocalizedStringWithFormat(
format,
DateUtil.dateFormatter.string(from: date),
)
}

View File

@ -70,8 +70,8 @@ class NotificationSettingsSoundViewController: OWSTableViewController2 {
for sound in Sounds.allNotificationSounds {
let soundName: String
if sound == .standard(.note) {
soundName = String(
format: OWSLocalizedString(
soundName = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"SETTINGS_AUDIO_DEFAULT_TONE_LABEL_FORMAT",
comment: "Format string for the default 'Note' sound. Embeds the system {{sound name}}.",
),

View File

@ -261,7 +261,7 @@ class PaymentsDetailViewController: OWSTableViewController2 {
)
}
}()
title = String(format: titleFormat, username)
title = String.nonPluralLocalizedStringWithFormat(titleFormat, username)
} else {
if paymentItem.isIncoming {
title = OWSLocalizedString(
@ -328,8 +328,8 @@ class PaymentsDetailViewController: OWSTableViewController2 {
"SETTINGS_PAYMENTS_PAYMENT_DETAILS_SENDER_FORMAT",
comment: "Format for the sender info in the payment details view in the app settings. Embeds {{ %1$@ the name of the sender of the payment, %2$@ the date the transaction was sent }}.",
)
value = String(
format: senderFormat,
value = String.nonPluralLocalizedStringWithFormat(
senderFormat,
sender,
TSPaymentModel.formatDate(mcLedgerBlockDate, isLongForm: true),
)
@ -470,7 +470,7 @@ class PaymentsDetailViewController: OWSTableViewController2 {
comment: "Format string for the recipient of an outgoing payment. Embeds: {{ the name of the recipient of the payment}}.",
),
)
usernameLabel.text = String(format: displayNameFormat, displayName)
usernameLabel.text = String.nonPluralLocalizedStringWithFormat(displayNameFormat, displayName)
}
let headerStack = UIStackView(arrangedSubviews: stackViews)

View File

@ -116,7 +116,7 @@ public class PaymentsRestoreWalletWordViewController: OWSViewController {
"SETTINGS_PAYMENTS_RESTORE_WALLET_WORD_INSTRUCTIONS_FORMAT",
comment: "Format for the instructions for the 'enter word' step of the 'restore payments wallet' views. Embeds {{ the index of the current word }}.",
)
let instructions = String(format: instructionsFormat, OWSFormat.formatInt(wordIndex + 1))
let instructions = String.nonPluralLocalizedStringWithFormat(instructionsFormat, OWSFormat.formatInt(wordIndex + 1))
let instructionsLabel = UILabel()
instructionsLabel.text = instructions
@ -152,8 +152,8 @@ public class PaymentsRestoreWalletWordViewController: OWSViewController {
comment: "Format for the placeholder text in the 'confirm payments passphrase' view of the app settings. Embeds: {{ the index of the word }}.",
)
let placeholder = NSAttributedString(
string: String(
format: placeholderFormat,
string: String.nonPluralLocalizedStringWithFormat(
placeholderFormat,
OWSFormat.formatInt(wordIndex + 1),
),
attributes: [

View File

@ -623,7 +623,7 @@ public class PaymentsSettingsViewController: OWSTableViewController2 {
"SETTINGS_PAYMENTS_BALANCE_CONVERSION_FORMAT",
comment: "Format string for the 'local balance converted into local currency' indicator. Embeds: {{ %1$@ the local balance in the local currency, %2$@ the local currency code, %3$@ the date the currency conversion rate was obtained. }}..",
)
return String(format: formatString, fiatAmountString, localCurrencyCode, conversionFreshnessString)
return String.nonPluralLocalizedStringWithFormat(formatString, fiatAmountString, localCurrencyCode, conversionFreshnessString)
}
private func configureHistorySection(

View File

@ -156,7 +156,7 @@ public class PaymentsViewPassphraseConfirmViewController: OWSTableViewController
"SETTINGS_PAYMENTS_VIEW_PASSPHRASE_CONFIRM_PLACEHOLDER_FORMAT",
comment: "Format for the placeholder text in the 'confirm payments passphrase' view of the app settings. Embeds: {{ the index of the word }}.",
)
textfield.placeholder = String(format: placeholderFormat, OWSFormat.formatInt(wordIndex + 1))
textfield.placeholder = String.nonPluralLocalizedStringWithFormat(placeholderFormat, OWSFormat.formatInt(wordIndex + 1))
}
configureTextfield(wordTextfield0, wordIndex: wordIndex0)
configureTextfield(wordTextfield1, wordIndex: wordIndex1)
@ -306,8 +306,8 @@ public class PaymentsViewPassphraseConfirmViewController: OWSTableViewController
"SETTINGS_PAYMENTS_VIEW_PASSPHRASE_CONFIRM_EXPLANATION_FORMAT",
comment: "Format for the explanation of the 'confirm payments passphrase word' step in the 'view payments passphrase' settings, indicating that the user needs to enter two words from their payments passphrase. Embeds: {{ %1$@ the index of the first word, %2$@ the index of the second word }}.",
)
let explanation = String(
format: explanationForm,
let explanation = String.nonPluralLocalizedStringWithFormat(
explanationForm,
OWSFormat.formatInt(wordIndex0 + 1),
OWSFormat.formatInt(wordIndex1 + 1),
)

View File

@ -103,7 +103,7 @@ class ProfileBioViewController: OWSTableViewController2 {
"PROFILE_BIO_VIEW_TITLE_FORMAT",
comment: "Title for the profile bio view. Embeds {{ the number of characters that can be added to the profile bio without hitting the length limit }}.",
)
title = String(format: titleFormat, OWSFormat.formatInt(remainingGlyphCount))
title = String.nonPluralLocalizedStringWithFormat(titleFormat, OWSFormat.formatInt(remainingGlyphCount))
} else {
title = OWSLocalizedString("PROFILE_BIO_VIEW_TITLE", comment: "Title for the profile bio view.")
}

View File

@ -551,8 +551,8 @@ class ProfileSettingsViewController: OWSTableViewController2 {
private func offerToDeleteUsername(currentUsername: String) {
OWSActionSheets.showConfirmationAlert(
message: String(
format: OWSLocalizedString(
message: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"PROFILE_SETTINGS_USERNAME_DELETION_CONFIRMATION_ALERT_MESSAGE_FORMAT",
comment: "A message asking the user if they are sure they want to remove their username and explaining what will happen. Embeds {{ the user's current username }}.",
),

View File

@ -468,7 +468,7 @@ class DatabaseRecoveryViewController<SetupResult>: OWSViewController {
owsFailDebug("Could not format the database size for some reason")
return String(currentDatabaseSize)
}()
return String(format: labelFormat, formattedBytes)
return String.nonPluralLocalizedStringWithFormat(labelFormat, formattedBytes)
}()
let continueButton = button(

View File

@ -92,7 +92,7 @@ class BadgeDetailsSheet: OWSTableSheetViewController {
"BADGE_DETAILS_TITLE_FOR_SUPPORTER",
comment: "When viewing someone else's donor badge, you'll see a sheet. This is the title on that sheet. Embeds {badge owner's short name}",
)
return String(format: format, remoteSupporterName)
return String.nonPluralLocalizedStringWithFormat(format, remoteSupporterName)
} else {
return self.focusedBadge.localizedName
}

View File

@ -58,7 +58,7 @@ class BadgeGiftingAlreadyRedeemedSheet: OWSTableSheetViewController {
titleLabel.textColor = .Signal.label
titleLabel.textAlignment = .center
titleLabel.numberOfLines = 0
titleLabel.text = String(format: titleFormat, self.shortName)
titleLabel.text = String.nonPluralLocalizedStringWithFormat(titleFormat, self.shortName)
stackView.addArrangedSubview(titleLabel)
stackView.setCustomSpacing(12, after: titleLabel)
@ -70,7 +70,7 @@ class BadgeGiftingAlreadyRedeemedSheet: OWSTableSheetViewController {
label.font = .dynamicTypeBody
label.textColor = .Signal.label
label.numberOfLines = 0
label.text = String(format: labelFormat, self.shortName)
label.text = String.nonPluralLocalizedStringWithFormat(labelFormat, self.shortName)
label.textAlignment = .center
stackView.addArrangedSubview(label)

View File

@ -77,7 +77,7 @@ class BadgeGiftingThanksSheet: OWSTableViewController2 {
"DONATION_ON_BEHALF_OF_A_FRIEND_THANKS_BODY_FORMAT",
comment: "When you donate on behalf of a friend, a thank-you sheet will appear. This is the text on that sheet. Embeds {{recipient name}}.",
)
let infoLabel = UILabel.explanationTextLabel(text: String(format: infoLabelFormat, recipientName))
let infoLabel = UILabel.explanationTextLabel(text: String.nonPluralLocalizedStringWithFormat(infoLabelFormat, recipientName))
cell.contentView.addSubview(infoLabel)
infoLabel.autoPinEdgesToSuperviewMargins()

View File

@ -117,7 +117,7 @@ public class BadgeIssueSheetState {
)
return Body(
String(format: formatText, chargeFailureString),
String.nonPluralLocalizedStringWithFormat(formatText, chargeFailureString),
learnMoreLink: URL.Support.Donations.badgeExpiration,
)
case let .boostExpired(hasCurrentSubscription):
@ -145,7 +145,7 @@ public class BadgeIssueSheetState {
"DONATION_FROM_A_FRIEND_BADGE_NOT_REDEEMED_BODY_FORMAT",
comment: "Someone donated on your behalf and you got a badge, which expired before you could redeem it. A sheet appears to tell you about this. This is the text on that sheet. Embeds {{contact name}}.",
)
return Body(String(format: formatText, fullName))
return Body(String.nonPluralLocalizedStringWithFormat(formatText, fullName))
case let .bankPaymentFailed(chargeFailureCode):
let formatText = OWSLocalizedString(
"DONATION_BADGE_ISSUE_SHEET_BANK_PAYMENT_FAILED_MESSAGE",
@ -157,7 +157,7 @@ public class BadgeIssueSheetState {
paymentMethod: .sepa,
)
return Body(String(format: formatText, chargeFailureString))
return Body(String.nonPluralLocalizedStringWithFormat(formatText, chargeFailureString))
case .boostBankPaymentProcessing:
let bodyFormat = OWSLocalizedString(
"DONATION_BADGE_ISSUE_SHEET_ONE_TIME_BANK_PAYMENT_PROCESSING_MESSAGE",
@ -165,7 +165,7 @@ public class BadgeIssueSheetState {
)
return Body(
String(format: bodyFormat, badge.localizedName),
String.nonPluralLocalizedStringWithFormat(bodyFormat, badge.localizedName),
learnMoreLink: URL.Support.Donations.donationPending,
)
case .subscriptionBankPaymentProcessing:
@ -175,7 +175,7 @@ public class BadgeIssueSheetState {
)
return Body(
String(format: bodyFormat, badge.localizedName),
String.nonPluralLocalizedStringWithFormat(bodyFormat, badge.localizedName),
learnMoreLink: URL.Support.Donations.donationPending,
)
}

View File

@ -264,7 +264,7 @@ class BadgeThanksSheet: OWSTableSheetViewController {
"DONATION_ON_BEHALF_OF_A_FRIEND_REDEEM_BADGE_TITLE_FORMAT",
comment: "A friend has donated on your behalf and you received a badge. A sheet opens for you to redeem this badge. Embeds {{contact's short name, such as a first name}}.",
)
return String(format: formatText, shortName)
return String.nonPluralLocalizedStringWithFormat(formatText, shortName)
}
}
@ -280,13 +280,13 @@ class BadgeThanksSheet: OWSTableSheetViewController {
"BADGE_THANKS_BODY",
comment: "When you make a donation to Signal, you will receive a badge. A thank-you sheet appears when this happens. This is the body text on that sheet.",
)
return String(format: formatText, self.badge.localizedName)
return String.nonPluralLocalizedStringWithFormat(formatText, self.badge.localizedName)
case let .giftReceived(shortName, _, _):
let formatText = OWSLocalizedString(
"DONATION_ON_BEHALF_OF_A_FRIEND_YOU_RECEIVED_A_BADGE_FORMAT",
comment: "A friend has donated on your behalf and you received a badge. This text says that you received a badge, and from whom. Embeds {{contact's short name, such as a first name}}.",
)
return String(format: formatText, shortName)
return String.nonPluralLocalizedStringWithFormat(formatText, shortName)
}
}

View File

@ -30,24 +30,24 @@ class DonateChoosePaymentMethodSheet: StackSheetViewController {
"DONATE_CHOOSE_PAYMENT_METHOD_SHEET_TITLE_FOR_ONE_TIME_DONATION",
comment: "When users make one-time donations, they see a sheet that lets them pick a payment method. This is the title on that sheet. Embeds {{amount of money}}, such as \"$5\".",
)
return String(format: format, currencyString)
return String.nonPluralLocalizedStringWithFormat(format, currencyString)
case .monthly:
let moneyPerMonthFormat = OWSLocalizedString(
"SUSTAINER_VIEW_PRICING",
comment: "Pricing text for sustainer view badges, embeds {{price}}",
)
let moneyPerMonthString = String(format: moneyPerMonthFormat, currencyString)
let moneyPerMonthString = String.nonPluralLocalizedStringWithFormat(moneyPerMonthFormat, currencyString)
let format = OWSLocalizedString(
"DONATE_CHOOSE_PAYMENT_METHOD_SHEET_TITLE_FOR_MONTHLY_DONATION",
comment: "When users make monthly donations, they see a sheet that lets them pick a payment method. This is the title on that sheet. Embeds {{amount of money per month}}, such as \"$5/month\".",
)
return String(format: format, moneyPerMonthString)
return String.nonPluralLocalizedStringWithFormat(format, moneyPerMonthString)
case .gift:
let format = OWSLocalizedString(
"DONATE_CHOOSE_PAYMENT_METHOD_SHEET_TITLE_FOR_DONATION_ON_BEHALF_OF_A_FRIEND",
comment: "When users make donations on a friend's behalf, they see a sheet that lets them pick a payment method. This is the title on that sheet. Embeds {{amount of money}}, such as \"$5\".",
)
return String(format: format, currencyString)
return String.nonPluralLocalizedStringWithFormat(format, currencyString)
}
}
@ -58,21 +58,21 @@ class DonateChoosePaymentMethodSheet: StackSheetViewController {
"DONATE_CHOOSE_PAYMENT_METHOD_SHEET_SUBTITLE_FOR_ONE_TIME_DONATION",
comment: "When users make one-time donations, they see a sheet that lets them pick a payment method. It also tells them what they'll be doing when they pay: receive a badge for a month. This is the subtitle on that sheet. Embeds {{localized badge name}}, such as \"Boost\".",
)
return String(format: format, badge.localizedName)
return String.nonPluralLocalizedStringWithFormat(format, badge.localizedName)
case .monthly:
let format = OWSLocalizedString(
"DONATE_CHOOSE_PAYMENT_METHOD_SHEET_SUBTITLE_FOR_MONTHLY_DONATION",
comment: "When users make monthly donations, they see a sheet that lets them pick a payment method. It also tells them what they'll be doing when they pay: receive a badge. This is the subtitle on that sheet. Embeds {{localized badge name}}, such as \"Planet\".",
)
return String(format: format, badge.localizedName)
return String.nonPluralLocalizedStringWithFormat(format, badge.localizedName)
case let .gift(recipientFullName):
let format = OWSLocalizedString(
"DONATE_CHOOSE_PAYMENT_METHOD_SHEET_SUBTITLE_FOR_DONATION_ON_BEHALF_OF_A_FRIEND",
comment: "When users make donations on a friend's behalf, they see a sheet that lets them pick a payment method. This is the subtitle on that sheet. Embeds {{recipient's name}}.",
)
return String(format: format, recipientFullName)
return String.nonPluralLocalizedStringWithFormat(format, recipientFullName)
}
}

View File

@ -113,7 +113,7 @@ extension DonateViewController {
owsFail("[Donations] No price for this currency code. This should be impossible in the UI")
}
let currencyString = CurrencyFormatter.format(money: price)
return String(format: format, currencyString)
return String.nonPluralLocalizedStringWithFormat(format, currencyString)
}()
headingStackView.removeAllSubviews()
headingStackView.addArrangedSubview(headingLabel)
@ -144,13 +144,13 @@ extension DonateViewController {
dateFormatter.dateStyle = .medium
let dateString = dateFormatter.string(from: currentSubscription.endOfCurrentPeriod)
return String(format: format, dateString)
return String.nonPluralLocalizedStringWithFormat(format, dateString)
} else {
let format = OWSLocalizedString(
"DONATE_SCREEN_MONTHLY_SUBSCRIPTION_SUBTITLE",
comment: "On the donation screen, you can see a list of monthly subscription levels. This text will be shown in the subtitle of each level, telling you which badge you'll get. Embeds {{localized badge name}}, such as \"Planet\".",
)
return String(format: format, subscriptionLevel.badge.localizedName)
return String.nonPluralLocalizedStringWithFormat(format, subscriptionLevel.badge.localizedName)
}
}()
}

View File

@ -334,8 +334,8 @@ class DonateViewController: OWSViewController, OWSNavigationChildController {
"DONATE_SCREEN_ERROR_TITLE_BANK_TRANSFER_AMOUNT_TOO_LARGE",
comment: "Title for an alert shown when the user tries to donate via bank transfer, but the amount they want to donate is too large.",
),
message: String(
format: messageFormat,
message: String.nonPluralLocalizedStringWithFormat(
messageFormat,
CurrencyFormatter.format(money: maximumAmount),
),
)
@ -474,7 +474,7 @@ class DonateViewController: OWSViewController, OWSNavigationChildController {
comment: "If the user tries to donate to Signal but they've entered an amount that's too small, this error message is shown. Embeds {{currency string}}, such as \"$5\".",
)
let currencyString = CurrencyFormatter.format(money: minimumAmount)
showError(String(format: format, currencyString))
showError(String.nonPluralLocalizedStringWithFormat(format, currencyString))
case .awaitingIDEALAuthorization:
// Not pending, but awaiting approval
let title = OWSLocalizedString(
@ -584,8 +584,8 @@ class DonateViewController: OWSViewController, OWSNavigationChildController {
"SUSTAINER_VIEW_UPDATE_SUBSCRIPTION_CONFIRMATION_TITLE",
comment: "Update Subscription? Action sheet title",
)
let message = String(
format: OWSLocalizedString(
let message = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"SUSTAINER_VIEW_UPDATE_SUBSCRIPTION_CONFIRMATION_MESSAGE",
comment: "Update Subscription? Action sheet message, embeds {{Price}}",
),

View File

@ -660,7 +660,7 @@ class DonationPaymentDetailsViewController: OWSTableViewController2 {
comment: "Users can donate to Signal with a credit or debit card. This is the heading on that screen, telling them how much they'll donate every month. Embeds {{formatted amount of money}}, such as \"$20\".",
)
}
return String(format: format, amountString)
return String.nonPluralLocalizedStringWithFormat(format, amountString)
}()
return UIButton(
@ -690,7 +690,7 @@ class DonationPaymentDetailsViewController: OWSTableViewController2 {
let oneEuroCentString = CurrencyFormatter.format(
money: FiatMoney(currencyCode: "EUR", value: 0.01),
)
let message = String(format: messageFormat, oneEuroCentString, amountString)
let message = String.nonPluralLocalizedStringWithFormat(messageFormat, oneEuroCentString, amountString)
let actionSheet = ActionSheetController(title: title, message: message)
actionSheet.addAction(.init(

View File

@ -184,16 +184,16 @@ public class GiftBadgeCellView: UIStackView {
return formattedDuration
}()
let formattedDurationText = String(
format: OWSLocalizedString(
let formattedDurationText = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"DONATION_FOR_A_FRIEND_ROW_DURATION",
comment: "When donating on behalf of a friend, a badge will be sent. This shows how long the badge lasts. Embeds {{formatted duration}}.",
),
formattedDuration,
)
return String(
format: OWSLocalizedString(
return String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"JOINED_WITH_DOT",
comment: "Two strings, joined by a dot. Embeds {first} and {second}, which are on opposite sides of the dot",
),

View File

@ -768,7 +768,7 @@ class ChatListCell: UITableViewCell, ReusableTableViewCell {
)
return .attributedText(
NSAttributedString(
string: String(format: addedToGroupFormat, addedToGroupByName),
string: String.nonPluralLocalizedStringWithFormat(addedToGroupFormat, addedToGroupByName),
attributes: [
.font: snippetFont,
.foregroundColor: snippetColor,

View File

@ -223,15 +223,15 @@ public class CLVBackupDownloadProgressView: BackupDownloadProgressView.Delegate
tintColor: UIColor.Signal.orange,
backgroundColor: UIColor(rgbHex: 0xF9E4B6),
),
title: String(
format: OWSLocalizedString(
title: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"RESTORING_MEDIA_DISK_SPACE_SHEET_TITLE_FORMAT",
comment: "Title shown on a bottom sheet for restoring media from a backup when paused because the device has insufficient disk space. Embeds {{ %@ formatted number of bytes downloaded, e.g. '100 MB' }}",
),
spaceRequiredString,
),
body: String(
format: OWSLocalizedString(
body: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"RESTORING_MEDIA_DISK_SPACE_SHEET_SUBTITLE_FORMAT",
comment: "Subtitle shown on a bottom sheet for restoring media from a backup when paused because the device has insufficient disk space. Embeds {{ %@ formatted number of bytes downloaded, e.g. '100 MB' }}",
),
@ -648,8 +648,8 @@ private class BackupDownloadProgressView: ChatListBackupProgressView {
"RESTORING_MEDIA_BANNER_TITLE",
comment: "Title shown on chat list banner for restoring media from a backup",
)
progressLabelText = String(
format: OWSLocalizedString(
progressLabelText = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"RESTORING_MEDIA_BANNER_PROGRESS_FORMAT",
comment: "Download progress for media from a backup. Embeds {{ %1$@ formatted number of bytes downloaded, e.g. '100 MB', %2$@ formatted total number of bytes to download, e.g. '3 GB' }}",
),
@ -667,8 +667,8 @@ private class BackupDownloadProgressView: ChatListBackupProgressView {
comment: "Title shown on chat list banner for restoring media from a backup when paused for some reason",
)
case .outOfDiskSpace(let spaceRequired):
titleLabelText = String(
format: OWSLocalizedString(
titleLabelText = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"RESTORING_MEDIA_BANNER_DISK_SPACE_TITLE_FORMAT",
comment: "Title shown on chat list banner for restoring media from a backup when paused because the device has insufficient disk space. Embeds {{ %@ formatted number of bytes downloaded, e.g. '100 MB' }}",
),

View File

@ -637,8 +637,8 @@ private class BackupExportProgressView: ChatListBackupProgressView {
"CHAT_LIST_BACKUP_PROGRESS_VIEW_UPLOADING_BACKUP_TITLE",
comment: "Title label shown in the chat list backup progress view while backup attachments are being uploaded.",
)
progressLabelText = String(
format: OWSLocalizedString(
progressLabelText = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"CHAT_LIST_BACKUP_PROGRESS_VIEW_UPLOAD_PROGRESS_FORMAT",
comment: "Progress label showing the amount uploaded out of the total. Embeds {{ %1$@ bytes uploaded, %2$@ total bytes to upload }}.",
),

View File

@ -387,8 +387,8 @@ extension ChatListViewController {
extension ChatListViewController: UsernameSelectionDelegate {
func usernameSelectionDidDismissAfterConfirmation(username: String) {
self.presentToast(
text: String(
format: OWSLocalizedString(
text: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"USERNAME_RESET_SUCCESSFUL_TOAST",
comment: "A message in a toast informing the user their username, link, and QR code have successfully been reset. Embeds {{ the user's new username }}.",
),

View File

@ -993,7 +993,7 @@ public class ChatListViewController: OWSViewController, HomeTabViewController {
"PAYMENTS_NOTIFICATION_BANNER_1_WITH_DETAILS_FORMAT",
comment: "Format for the payments notification banner for a single payment notification with details. Embeds: {{ %1$@ the name of the user who sent you the payment, %2$@ the amount of the payment }}.",
)
let title = String(format: format, shortName, formattedAmount)
let title = String.nonPluralLocalizedStringWithFormat(format, shortName, formattedAmount)
let avatarView = ConversationAvatarView(sizeClass: .customDiameter(Self.paymentsBannerAvatarSize), localUserDisplayMode: .asUser)
avatarView.update(transaction) { config in
@ -1029,7 +1029,7 @@ public class ChatListViewController: OWSViewController, HomeTabViewController {
"PAYMENTS_NOTIFICATION_BANNER_N_FORMAT",
comment: "Format for the payments notification banner for multiple payment notifications. Embeds: {{ the number of unread payment notifications }}.",
)
title = String(format: format, OWSFormat.formatUInt(unreadCount))
title = String.nonPluralLocalizedStringWithFormat(format, OWSFormat.formatUInt(unreadCount))
}
let iconView = UIImageView.withTemplateImageName(
@ -1273,8 +1273,8 @@ public class ChatListViewController: OWSViewController, HomeTabViewController {
"NOTIFICATIONS_ERROR_TITLE",
comment: "Shown as the title of an alert when notifications can't be shown due to an error.",
),
message: String(
format: OWSLocalizedString(
message: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"NOTIFICATIONS_ERROR_MESSAGE",
comment: "Shown as the body of an alert when notifications can't be shown due to an error.",
),

View File

@ -720,7 +720,7 @@ class EmptySearchResultCell: UITableViewCell {
"HOME_VIEW_SEARCH_NO_RESULTS_FORMAT",
comment: "Format string when search returns no results. Embeds {{search term}}",
)
messageLabel.text = String(format: format, searchText)
messageLabel.text = String.nonPluralLocalizedStringWithFormat(format, searchText)
messageLabel.textColor = .Signal.label
messageLabel.font = UIFont.dynamicTypeBody

View File

@ -263,8 +263,8 @@ extension StoryContextMenuGenerator {
comment: "Title asking the user if they are sure they want to hide stories from another user",
),
message: loadThreadDisplayNameWithSneakyTransaction(context: context).map {
String(
format: OWSLocalizedString(
String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"STORIES_HIDE_STORY_ACTION_SHEET_MESSAGE",
comment: "Message asking the user if they are sure they want to hide stories from {{other user's name}}",
),

View File

@ -231,7 +231,7 @@ class StoryReplyInputToolbar: UIView {
let authorName = SSKEnvironment.shared.databaseStorageRef.read { tx in
return SSKEnvironment.shared.contactManagerRef.displayName(for: quotedReplyModel.originalMessageAuthorAddress, tx: tx).resolvedValue()
}
return String(format: format, authorName)
return String.nonPluralLocalizedStringWithFormat(format, authorName)
} else {
return OWSLocalizedString(
"STORY_REPLY_TEXT_FIELD_PLACEHOLDER",

View File

@ -349,7 +349,7 @@ public class NewGroupConfirmViewController: OWSTableViewController2 {
"GROUP_INVITES_SENT_ALERT_MESSAGE_1_FORMAT",
comment: "Format for the message for an alert indicating that a member was invited to a group. Embeds: {{ the name of the member. }}",
)
alertMessage = String(format: alertMessageFormat, inviteeName)
alertMessage = String.nonPluralLocalizedStringWithFormat(alertMessageFormat, inviteeName)
}
let actionSheet = ActionSheetController(title: alertTitle, message: alertMessage)

View File

@ -433,7 +433,7 @@ public class SendPaymentCompletionActionSheet: ActionSheetController {
let row = addRow(
to: &topGroup,
title: String(format: fiatFormat, currencyConversion.currencyCode),
title: String.nonPluralLocalizedStringWithFormat(fiatFormat, currencyConversion.currencyCode),
value: fiatAmountString,
titleIconView: currencyConversionInfoView,
addSeparator: true,
@ -497,7 +497,7 @@ public class SendPaymentCompletionActionSheet: ActionSheetController {
"PAYMENTS_NEW_PAYMENT_RECIPIENT_AMOUNT_FORMAT",
comment: "Format for the 'payment recipient amount' indicator. Embeds {{ the name of the recipient of the payment }}.",
)
return String(format: userFormat, otherUserName)
return String.nonPluralLocalizedStringWithFormat(userFormat, otherUserName)
}
public static func formatPaymentFailure(_ error: Error, withErrorPrefix: Bool) -> String {
@ -515,7 +515,7 @@ public class SendPaymentCompletionActionSheet: ActionSheetController {
"PAYMENTS_NEW_PAYMENT_ERROR_INSUFFICIENT_FUNDS_FORMAT",
comment: "Indicates that a payment failed due to insufficient funds. Embeds {{ current balance }}.",
)
return String(format: format, formattedBalance)
return String.nonPluralLocalizedStringWithFormat(format, formattedBalance)
} else {
return OWSLocalizedString(
"PAYMENTS_NEW_PAYMENT_ERROR_INSUFFICIENT_FUNDS",

View File

@ -154,8 +154,8 @@ class SendPaymentHelper {
"PAYMENTS_NEW_PAYMENT_BALANCE_FORMAT",
comment: "Format for the 'balance' indicator. Embeds {{ the current payments balance }}.",
)
balanceLabel.text = String(
format: format,
balanceLabel.text = String.nonPluralLocalizedStringWithFormat(
format,
Self.formatMobileCoinAmount(maximumPaymentAmount),
)
}
@ -218,8 +218,8 @@ class SendPaymentHelper {
"PAYMENTS_NEW_PAYMENT_CURRENCY_FORMAT",
comment: "Format for currency amounts in the 'send payment' UI. Embeds {{ %1$@ the current payments balance, %2$@ the currency indicator }}.",
)
return String(
format: format,
return String.nonPluralLocalizedStringWithFormat(
format,
formattedAmount,
PaymentsConstants.mobileCoinCurrencyIdentifier,
)

View File

@ -143,8 +143,8 @@ public class SendPaymentMemoViewController: OWSViewController {
"PAYMENTS_NEW_PAYMENT_MESSAGE_COUNT_FORMAT",
comment: "Format for the 'message character count indicator' for the 'new payment or payment request' view. Embeds {{ %1$@ the number of characters in the message, %2$@ the maximum number of characters in the message }}.",
)
memoCharacterCountLabel.text = String(
format: format,
memoCharacterCountLabel.text = String.nonPluralLocalizedStringWithFormat(
format,
OWSFormat.formatInt(strippedMemoMessage.count),
OWSFormat.formatInt(PaymentsImpl.maxPaymentMemoMessageLength),
)

View File

@ -294,7 +294,7 @@ public class SendPaymentViewController: OWSViewController {
let recipientName: String = SSKEnvironment.shared.databaseStorageRef.read { tx in
SSKEnvironment.shared.contactManagerRef.displayName(for: recipientAddress, tx: tx).resolvedValue()
}
let title = String(format: titleFormat, recipientName)
let title = String.nonPluralLocalizedStringWithFormat(titleFormat, recipientName)
let actionSheet = ActionSheetController(
title: title,
message: OWSLocalizedString(
@ -849,7 +849,7 @@ public class SendPaymentViewController: OWSViewController {
"PAYMENTS_CURRENCY_CONVERSION_FRESHNESS_FORMAT",
comment: "Format for indicator of a payment amount converted to fiat currency with the freshness of the conversion rate. Embeds: {{ %1$@ the payment amount, %2$@ the freshness of the currency conversion rate }}.",
)
return String(format: conversionFormat, formattedAmount, formattedFreshness)
return String.nonPluralLocalizedStringWithFormat(conversionFormat, formattedAmount, formattedFreshness)
}
private func updateBalanceLabel() {
@ -1068,7 +1068,7 @@ public class SendPaymentViewController: OWSViewController {
"SETTINGS_PAYMENTS_PAYMENT_INSUFFICIENT_BALANCE_ALERT_MESSAGE_FORMAT",
comment: "Message for the 'insufficient balance for payment' alert. Embeds: {{ The current payments balance }}.",
)
let message = String(format: messageFormat, PaymentsFormat.format(
let message = String.nonPluralLocalizedStringWithFormat(messageFormat, PaymentsFormat.format(
paymentAmount: paymentBalance.amount,
isShortForm: false,
withCurrencyCode: true,

View File

@ -1916,7 +1916,7 @@ extension CameraZoomSelectionControl {
"CAMERA_VO_ZOOM_LEVEL",
comment: "VoiceOver description of current camera zoom level.",
)
return String(format: formatString, zoomValueString)
return String.nonPluralLocalizedStringWithFormat(formatString, zoomValueString)
}
set { super.accessibilityValue = newValue }
}

View File

@ -1433,15 +1433,15 @@ extension PhotoCaptureViewController: QRCodeSampleBufferScannerDelegate {
// check after the username is queried before showing the sheet.
guard isViewVisible else { return }
OWSActionSheets.showConfirmationAlert(
title: String(
format: OWSLocalizedString(
title: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"PHOTO_CAPTURE_USERNAME_QR_CODE_FOUND_TITLE_FORMAT",
comment: "Title for sheet presented from photo capture view indicating that a username QR code was found. Embeds {{username}}.",
),
username,
),
message: String(
format: OWSLocalizedString(
message: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"PHOTO_CAPTURE_USERNAME_QR_CODE_FOUND_MESSAGE_FORMAT",
comment: "Message for a sheet presented from photo capture view indicating that a username QR code was found. Embeds {{username}}.",
),

View File

@ -265,8 +265,8 @@ class NewPollViewController2: OWSViewController, UITableViewDelegate, OWSNavigat
cell.configure(
text: optionRows[rowIndex].text,
placeholder: String(
format: OWSLocalizedString(
placeholder: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"POLL_OPTION_PLACEHOLDER_FORMAT",
comment: #"Format text for the placeholder of an option row when creating a poll. Embeds {{ the number of this option in a list, as a pre-localized string }}, so it should look like "Option 1", "Option 2"."#,
),

View File

@ -126,7 +126,7 @@ public class AddToGroupViewController: OWSTableViewController2 {
"ADD_TO_GROUP_ACTION_SHEET_TITLE",
comment: "The title on the 'add to group' confirmation action sheet.",
),
message: String(format: messageFormat, shortName, groupThread.groupNameOrDefault),
message: String.nonPluralLocalizedStringWithFormat(messageFormat, shortName, groupThread.groupNameOrDefault),
proceedTitle: OWSLocalizedString(
"ADD_TO_GROUP_ACTION_PROCEED_BUTTON",
comment: "The button on the 'add to group' confirmation to add the user to the group.",
@ -168,7 +168,7 @@ public class AddToGroupViewController: OWSTableViewController2 {
"ADD_TO_GROUP_SUCCESS_TOAST_FORMAT",
comment: "A toast on the 'add to group' view indicating the user was added. Embeds {contact name} and {group name}",
)
let toastText = String(format: toastFormat, shortName, groupThread.groupNameOrDefault)
let toastText = String.nonPluralLocalizedStringWithFormat(toastFormat, shortName, groupThread.groupNameOrDefault)
presentingViewController?.presentToast(text: toastText)
}
}

View File

@ -173,8 +173,8 @@ class ContactAboutSheet: StackSheetViewController {
let nameLabel = self.nameLabel
else { return }
Tooltip(
message: String(
format: OWSLocalizedString(
message: String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"CONTACT_ABOUT_SHEET_SECONDARY_NAME_TOOLTIP_MESSAGE",
comment: "Message for a tooltip that appears above a parenthesized name for another user, indicating that that name is the name the other user set for themself. Embeds {{name}}",
),

View File

@ -24,7 +24,7 @@ class LegacyGroupView: UIView {
private let label = UILabel()
private func configureLabel(format: String, highlightedSubstring: String) {
let text = String(format: format, highlightedSubstring)
let text = String.nonPluralLocalizedStringWithFormat(format, highlightedSubstring)
let attributedString = NSMutableAttributedString(string: text)
attributedString.setAttributes(

View File

@ -627,8 +627,8 @@ class ConversationSettingsViewController: OWSTableViewController2, BadgeCollecti
return
}
let confirmationTitle = String(
format: OWSLocalizedString(
let confirmationTitle = String.nonPluralLocalizedStringWithFormat(
OWSLocalizedString(
"END_GROUP_LABEL_SPECIFIC",
comment: "End group confirmation title for a specific group. Embeds {{ group name }}",
),
@ -772,7 +772,7 @@ class ConversationSettingsViewController: OWSTableViewController2, BadgeCollecti
"CONVERSATION_SETTINGS_MUTED_UNTIL_UNMUTE_FORMAT",
comment: "Indicates that this thread is muted until a given date or time. Embeds {{The date or time which the thread is muted until}}.",
)
return String(format: formatString, dateFormatter.string(from: mutedUntilDate))
return String.nonPluralLocalizedStringWithFormat(formatString, dateFormatter.string(from: mutedUntilDate))
}
private class func muteUnmuteActions(

View File

@ -113,7 +113,7 @@ class GroupDescriptionViewController: OWSTableViewController2 {
"GROUP_DESCRIPTION_VIEW_TITLE_FORMAT",
comment: "Title for the group description view. Embeds {{ the number of characters that can be added to the description without hitting the length limit }}.",
)
title = String(format: titleFormat, OWSFormat.formatInt(remainingGlyphCount))
title = String.nonPluralLocalizedStringWithFormat(titleFormat, OWSFormat.formatInt(remainingGlyphCount))
} else {
title = OWSLocalizedString(
"GROUP_DESCRIPTION_VIEW_TITLE",

View File

@ -159,7 +159,7 @@ public class GroupMemberRequestsAndInvitesViewController: OWSTableViewController
comment: "Footer for the 'pending member requests' section of the 'member requests and invites' view. Embeds {{ the name of the group }}.",
)
let groupName = SSKEnvironment.shared.databaseStorageRef.read { tx in SSKEnvironment.shared.contactManagerRef.displayName(for: oldGroupThread, transaction: tx) }
section.footerTitle = String(format: footerFormat, groupName)
section.footerTitle = String.nonPluralLocalizedStringWithFormat(footerFormat, groupName)
if !requestingMembersSorted.isEmpty {
for address in requestingMembersSorted {
@ -450,7 +450,7 @@ public class GroupMemberRequestsAndInvitesViewController: OWSTableViewController
"PENDING_GROUP_MEMBERS_REVOKE_LOCAL_INVITE_CONFIRMATION_TITLE_1_FORMAT",
comment: "Format for title of 'revoke invite' confirmation alert. Embeds {{ the name of the invited group member. }}.",
)
let alertTitle = String(format: format, invitedName)
let alertTitle = String.nonPluralLocalizedStringWithFormat(format, invitedName)
let actionSheet = ActionSheetController(title: alertTitle)
actionSheet.addAction(ActionSheetAction(
title: OWSLocalizedString(
@ -530,7 +530,7 @@ public class GroupMemberRequestsAndInvitesViewController: OWSTableViewController
comment: "Message indicating that a request to join the group was successfully approved. Embeds {{ the name of the approved user }}.",
)
let userName = SSKEnvironment.shared.databaseStorageRef.read { tx in SSKEnvironment.shared.contactManagerRef.displayName(for: address, tx: tx).resolvedValue() }
let text = String(format: format, userName)
let text = String.nonPluralLocalizedStringWithFormat(format, userName)
presentToast(text: text)
}
@ -540,7 +540,7 @@ public class GroupMemberRequestsAndInvitesViewController: OWSTableViewController
comment: "Message indicating that a request to join the group was successfully denied. Embeds {{ the name of the denied user }}.",
)
let userName = SSKEnvironment.shared.databaseStorageRef.read { tx in SSKEnvironment.shared.contactManagerRef.displayName(for: address, tx: tx).resolvedValue() }
let text = String(format: format, userName)
let text = String.nonPluralLocalizedStringWithFormat(format, userName)
presentToast(text: text)
}
}
@ -596,7 +596,7 @@ private extension GroupMemberRequestsAndInvitesViewController {
"PENDING_GROUP_MEMBERS_ACCEPT_REQUEST_CONFIRMATION_TITLE_FORMAT",
comment: "Title of 'accept member request to join group' confirmation alert. Embeds {{ the name of the requesting group member. }}.",
)
let alertTitle = String(format: format, username)
let alertTitle = String.nonPluralLocalizedStringWithFormat(format, username)
let actionSheet = ActionSheetController(title: alertTitle)
let actionTitle = OWSLocalizedString(
@ -618,7 +618,7 @@ private extension GroupMemberRequestsAndInvitesViewController {
"PENDING_GROUP_MEMBERS_DENY_REQUEST_CONFIRMATION_TITLE_FORMAT",
comment: "Title of 'deny member request to join group' confirmation alert. Embeds {{ the name of the requesting group member. }}.",
)
let alertTitle = String(format: format, username)
let alertTitle = String.nonPluralLocalizedStringWithFormat(format, username)
let actionSheet = ActionSheetController(title: alertTitle)
let actionTitle = OWSLocalizedString(

View File

@ -43,7 +43,7 @@ extension GroupViewHelper {
},
)
}
let title = String(format: titleFormat, SSKEnvironment.shared.databaseStorageRef.read { tx in
let title = String.nonPluralLocalizedStringWithFormat(titleFormat, SSKEnvironment.shared.databaseStorageRef.read { tx in
return SSKEnvironment.shared.contactManagerRef.displayName(for: address, tx: tx).resolvedValue()
})
let actionSheet = ActionSheetController(title: title, message: message)

View File

@ -95,8 +95,8 @@ class SoundAndNotificationsSettingsViewController: OWSTableViewController2 {
"CONVERSATION_SETTINGS_MUTED_UNTIL_FORMAT",
comment: "Indicates that this thread is muted until a given date or time. Embeds {{The date or time which the thread is muted until}}.",
)
muteStatus = String(
format: formatString,
muteStatus = String.nonPluralLocalizedStringWithFormat(
formatString,
dateFormatter.string(from: mutedUntilDate),
)
}

View File

@ -197,7 +197,7 @@ class PreviewWallpaperViewController: UIViewController {
comment: "The outgoing bubble text when setting a wallpaper for specific chat. Embeds {{chat name}}",
)
let displayName = SSKEnvironment.shared.databaseStorageRef.read { tx in SSKEnvironment.shared.contactManagerRef.displayName(for: thread, transaction: tx) }
return String(format: formatString, displayName)
return String.nonPluralLocalizedStringWithFormat(formatString, displayName)
}()
let incomingText: String

Some files were not shown because too many files have changed in this diff Show More