From 700a71f3930cf1d926d77915656c3de6fd27cd48 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Wed, 9 Apr 2025 18:21:11 +0400 Subject: [PATCH] Update localization --- .../Telegram-iOS/en.lproj/Localizable.strings | 65 +++++++++++++++++++ .../CallListUI/Sources/CallListCallItem.swift | 6 +- .../Sources/CallListController.swift | 9 +-- .../Sources/CallListControllerNode.swift | 6 +- .../Sources/Node/ChatListItemStrings.swift | 22 ++++++- .../Sources/ContextActionsContainerNode.swift | 3 +- .../Sources/InviteLinkInviteController.swift | 8 +-- .../ItemListPermanentInviteLinkItem.swift | 3 +- .../Sources/JoinLinkPreviewController.swift | 4 +- .../QrCodeUI/Sources/QrCodeScreen.swift | 3 +- .../Sources/CallController.swift | 7 +- .../Sources/PresentationCall.swift | 4 +- .../Sources/PresentationGroupCall.swift | 3 +- .../VideoChatEncryptionKeyComponent.swift | 7 +- .../VideoChatParticipantsComponent.swift | 16 +---- .../Sources/VideoChatScreen.swift | 6 +- .../VideoChatScreenInviteMembers.swift | 3 - .../Sources/VoiceChatController.swift | 3 - .../State/ConferenceCallE2EContext.swift | 2 - .../Sources/ServiceMessageStrings.swift | 24 ++++++- .../ChatMessageCallBubbleContentNode.swift | 16 ++--- .../ChatMessageWebpageBubbleContentNode.swift | 3 +- .../Sources/JoinSubjectScreen.swift | 23 +++---- .../TelegramUI/Sources/AppDelegate.swift | 18 +++-- .../TelegramUI/Sources/ChatController.swift | 3 +- .../ContactMultiselectionControllerNode.swift | 10 ++- .../TelegramUI/Sources/OpenResolvedUrl.swift | 3 +- .../Sources/SharedAccountContext.swift | 14 ++-- 28 files changed, 173 insertions(+), 121 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 2857455b4d..f6e9611518 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14115,3 +14115,68 @@ Sorry for the inconvenience."; "WebApp.ImportData.AccountHeader" = "ACCOUNT TO IMPORT DATA FROM"; "WebApp.ImportData.CreatedOn" = "created on %@"; "WebApp.ImportData.Import" = "Import"; + +"CallList.ToastCallLinkCopied.Text" = "Call link copied"; +"CallList.ToastCallLinkCopied.Action" = "View Call"; +"CallList.NewCall" = "New Call"; +"CallList.NewCallLink" = "New Call Link"; + +"Chat.SendStarsToBecomeTopInfo" = "Send %@ or more to highlight your profile"; + +"VideoChat.RevokeLink" = "Revoke Link"; + +"InviteLink.GroupCallLinkHelp" = "Anyone on Telegram can join your call by following the link below."; +"InviteLink.CallLinkTitle" = "Call Link"; +"InviteLink.CreatedGroupCallFooter" = "Be the first to join the call and add people from there. [Open Call >](open_call)"; +"InviteLink.QRCode.InfoGroupCall" = "Everyone on Telegram can scan this code to join your group call."; + +"Call.GenericGroupCallTitle" = "Group Call"; + +"VideoChat.EncryptionKeyLabel" = "End-to-end encrypted"; +"VideoChat.EncryptionKeyText" = "These four emojis represent the call's encryption key. They must match for all participants and change when someone joins or leaves."; +"VideoChat.EncryptionKeyDone" = "Close"; +"VideoChat.InviteMember" = "Add Member"; + +"VideoChat.GroupCallTitle" = "Group Call"; + +"Chat.ViewGroupCall" = "JOIN GROUP CALL"; + +"NewCall.SearchPlaceholder" = "Search for contacts or usernames"; +"NewCall.VideoOption" = "Call with video enabled"; +"NewCall.ActionCallSingle" = "Call %@"; +"NewCall.ActionCallMultiple" = "Call"; + +"Chat.ToastCallLinkExpired.Text" = "This link is no longer active"; + +"Chat.CallMessage.GroupCallParticipantCount_1" = "1 person"; +"Chat.CallMessage.GroupCallParticipantCount_any" = "%d people"; + +"Chat.CallMessage.DeclinedGroupCall" = "Declined Group Call"; +"Chat.CallMessage.MissedGroupCall" = "Missed Group Call"; +"Chat.CallMessage.CancelledGroupCall" = "Cancelled Group Call"; +"Chat.CallMessage.IncomingGroupCall" = "Incoming Group Call"; +"Chat.CallMessage.OutgoingGroupCall" = "Outgoing Group Call"; + +"Invitation.GroupCall" = "Group Call"; +"Invitation.JoinGroupCall" = "Join Group Call"; +"Invitation.PublicGroup" = "public group"; +"Invitation.PrivateGroup" = "private group"; + +"Invitation.GroupCall.Text" = "You are invited to join a group call."; + +"Invitation.Group.AlreadyJoinedSingle" = "**%@** already joined this group."; +"Invitation.Group.AlreadyJoinedMultiple" = "%@ already joined this group."; +"Invitation.Group.AlreadyJoinedMultipleWithCount_1" = "{} and **%d** other person already joined this group."; +"Invitation.Group.AlreadyJoinedMultipleWithCount_any" = "{} and **%d** other people already joined this group."; + +"Invitation.GroupCall.AlreadyJoinedSingle" = "**%@** already joined this call."; +"Invitation.GroupCall.AlreadyJoinedMultiple" = "%@ already joined this call."; +"Invitation.GroupCall.AlreadyJoinedMultipleWithCount_1" = "{} and **%d** other person already joined this call."; +"Invitation.GroupCall.AlreadyJoinedMultipleWithCount_any" = "{} and **%d** other people already joined this call."; + +"Call.ShareLink" = "Share Call Link"; +"Call.AddMemberTitle" = "Add Member"; + +"Call.IncomingGroupCallTitle.Single" = "%@"; +"Call.IncomingGroupCallTitle.Multiple_1" = "{} and 1 other"; +"Call.IncomingGroupCallTitle.Multiple_any" = "{} and %d others"; \ No newline at end of file diff --git a/submodules/CallListUI/Sources/CallListCallItem.swift b/submodules/CallListUI/Sources/CallListCallItem.swift index a408a63cbc..17ae28f7d7 100644 --- a/submodules/CallListUI/Sources/CallListCallItem.swift +++ b/submodules/CallListUI/Sources/CallListCallItem.swift @@ -425,9 +425,8 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode { isVideo = conferenceCall.flags.contains(.isVideo) if message.flags.contains(.Incoming) { hasIncoming = true - //TODO:localize let missedTimeout: Int32 - #if DEBUG + #if DEBUG && false missedTimeout = 5 #else missedTimeout = 30 @@ -463,8 +462,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode { peersString.append(", ") } if peer.id == item.context.account.peerId { - //TODO:localize - peersString += "You" + peersString += item.presentationData.strings.DialogList_You } else { peersString += peer.compactDisplayTitle } diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index 89191fcbf6..246fe5d891 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -297,9 +297,8 @@ public final class CallListController: TelegramBaseController { if let result { switch result { case .linkCopied: - //TODO:localize let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - self.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: "Call link copied.", customUndoText: "View Call", timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in + self.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: presentationData.strings.CallList_ToastCallLinkCopied_Text, customUndoText: presentationData.strings.CallList_ToastCallLinkCopied_Action, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in if case .undo = action { openCall() } @@ -517,8 +516,7 @@ public final class CallListController: TelegramBaseController { return } - //TODO:localize - let options = [ContactListAdditionalOption(title: "New Call Link", icon: .generic(PresentationResourcesItemList.linkIcon(presentationData.theme)!), action: { [weak self] in + let options = [ContactListAdditionalOption(title: self.presentationData.strings.CallList_NewCallLink, icon: .generic(PresentationResourcesItemList.linkIcon(presentationData.theme)!), action: { [weak self] in guard let self else { return } @@ -741,8 +739,7 @@ public final class CallListController: TelegramBaseController { self.context.sharedContext.openCreateGroupCallUI(context: self.context, peerIds: conferenceCall.otherParticipants, parentController: self) default: let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - //TODO:localize - self.present(textAlertController(context: self.context, title: nil, text: "An error occurred", actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + self.present(textAlertController(context: self.context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } }) } diff --git a/submodules/CallListUI/Sources/CallListControllerNode.swift b/submodules/CallListUI/Sources/CallListControllerNode.swift index 05fe89d854..74b1c7280a 100644 --- a/submodules/CallListUI/Sources/CallListControllerNode.swift +++ b/submodules/CallListUI/Sources/CallListControllerNode.swift @@ -126,8 +126,7 @@ private func mappedInsertEntries(context: AccountContext, presentationData: Item case let .displayTabInfo(_, text): return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint) case .openNewCall: - //TODO:localize - let item = ItemListPeerActionItem(presentationData: presentationData, style: showSettings ? .blocks : .plain, icon: .none, title: "New Call", hasSeparator: false, sectionId: 1, height: .generic, noInsets: true, editing: false, action: { + let item = ItemListPeerActionItem(presentationData: presentationData, style: showSettings ? .blocks : .plain, icon: .none, title: presentationData.strings.CallList_NewCall, hasSeparator: false, sectionId: 1, height: .generic, noInsets: true, editing: false, action: { nodeInteraction.openNewCall() }) return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) @@ -151,8 +150,7 @@ private func mappedUpdateEntries(context: AccountContext, presentationData: Item case let .displayTabInfo(_, text): return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint) case .openNewCall: - //TODO:localize - let item = ItemListPeerActionItem(presentationData: presentationData, icon: .none, title: "New Call", sectionId: 1, height: .generic, noInsets: true, editing: false, action: { + let item = ItemListPeerActionItem(presentationData: presentationData, icon: .none, title: presentationData.strings.CallList_NewCall, sectionId: 1, height: .generic, noInsets: true, editing: false, action: { nodeInteraction.openNewCall() }) return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) diff --git a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift index d989d3e693..1e7012154e 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift @@ -298,9 +298,25 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: messageText = invoice.title case let action as TelegramMediaAction: switch action.action { - case .conferenceCall: - //TODO:localize - messageText = "Group call" + case let .conferenceCall(conferenceCall): + let incoming = message.flags.contains(.Incoming) + + let missedTimeout: Int32 = 30 + let currentTime = Int32(Date().timeIntervalSince1970) + + if conferenceCall.flags.contains(.isMissed) { + messageText = strings.Chat_CallMessage_DeclinedGroupCall + } else if message.timestamp < currentTime - missedTimeout { + messageText = strings.Chat_CallMessage_MissedGroupCall + } else if conferenceCall.duration != nil { + messageText = strings.Chat_CallMessage_CancelledGroupCall + } else { + if incoming { + messageText = strings.Chat_CallMessage_IncomingGroupCall + } else { + messageText = strings.Chat_CallMessage_OutgoingGroupCall + } + } case let .phoneCall(_, discardReason, _, isVideo): hideAuthor = !isPeerGroup let incoming = message.flags.contains(.Incoming) diff --git a/submodules/ContextUI/Sources/ContextActionsContainerNode.swift b/submodules/ContextUI/Sources/ContextActionsContainerNode.swift index 8641e4d77c..e4f72eda92 100644 --- a/submodules/ContextUI/Sources/ContextActionsContainerNode.swift +++ b/submodules/ContextUI/Sources/ContextActionsContainerNode.swift @@ -438,9 +438,8 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode { icon = nil isUserInteractionEnabled = action != nil case let .starsReactions(topCount): - //TODO:localize self.action = nil - self.text = "Send \(topCount) or more to highlight your profile" + self.text = self.presentationData.strings.Chat_SendStarsToBecomeTopInfo("\(topCount)").string self.targetSelectionIndex = nil icon = nil isUserInteractionEnabled = action != nil diff --git a/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift b/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift index cf901db89a..153bcc6d57 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift @@ -492,10 +492,9 @@ public final class InviteLinkInviteController: ViewController { let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() } - //TODO:localize controller.setItemGroups([ ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: "Revoke Link"), + ActionSheetTextItem(title: presentationData.strings.VideoChat_RevokeLink), ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { [weak self] in dismissAction() @@ -674,9 +673,8 @@ public final class InviteLinkInviteController: ViewController { } var entries: [InviteLinkInviteEntry] = [] - //TODO:localize - let helpText: String = "Anyone on Telegram can join your call by following the link below." - entries.append(.header(title: "Call Link", text: helpText)) + let helpText: String = presentationData.strings.InviteLink_GroupCallLinkHelp + entries.append(.header(title: presentationData.strings.InviteLink_CallLinkTitle, text: helpText)) let mainInvite: ExportedInvitation = .link(link: mainInvite?.link ?? "", title: nil, isPermanent: true, requestApproval: false, isRevoked: false, adminId: self.context.account.peerId, date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil, requestedCount: nil, pricing: nil) diff --git a/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift b/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift index e64aa14847..de6d03c3a7 100644 --- a/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift @@ -369,8 +369,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem return (TelegramTextAttributes.URL, contents) } ) - //TODO:localize - let justCreatedCallTextAttributedString = parseMarkdownIntoAttributedString("Be the first to join the call and add people from there. [Open Call >](open_call)", attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString + let justCreatedCallTextAttributedString = parseMarkdownIntoAttributedString(item.presentationData.strings.InviteLink_CreatedGroupCallFooter, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString if let range = justCreatedCallTextAttributedString.string.range(of: ">"), let chevronImage { justCreatedCallTextAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: justCreatedCallTextAttributedString.string)) } diff --git a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift index 91c373bc47..5b23c18ed4 100644 --- a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift +++ b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift @@ -193,9 +193,7 @@ public func JoinLinkPreviewController( ) -> ViewController { if let data = context.currentAppConfiguration.with({ $0 }).data, data["ios_killswitch_legacy_join_link"] != nil { return LegacyJoinLinkPreviewController(context: context, link: link, navigateToPeer: navigateToPeer, parentNavigationController: parentNavigationController, resolvedState: resolvedState) - } else if case let .invite(invite) = resolvedState, !invite.flags.requestNeeded, !invite.flags.isBroadcast, !invite.flags.canRefulfillSubscription { - //TODO:release - + } else if case let .invite(invite) = resolvedState, !invite.flags.requestNeeded, !invite.flags.isBroadcast, !invite.flags.canRefulfillSubscription { var verificationStatus: JoinSubjectScreenMode.Group.VerificationStatus? if invite.flags.isFake { verificationStatus = .fake diff --git a/submodules/QrCodeUI/Sources/QrCodeScreen.swift b/submodules/QrCodeUI/Sources/QrCodeScreen.swift index 69e11b3c2e..c5630feda9 100644 --- a/submodules/QrCodeUI/Sources/QrCodeScreen.swift +++ b/submodules/QrCodeUI/Sources/QrCodeScreen.swift @@ -253,8 +253,7 @@ public final class QrCodeScreen: ViewController { case .channel: text = self.presentationData.strings.InviteLink_QRCode_InfoChannel case .groupCall: - //TODO:localize - text = "Everyone on Telegram can scan this code to join your group call." + text = self.presentationData.strings.InviteLink_QRCode_InfoGroupCall } case .chatFolder: title = self.presentationData.strings.InviteLink_QRCodeFolder_Title diff --git a/submodules/TelegramCallsUI/Sources/CallController.swift b/submodules/TelegramCallsUI/Sources/CallController.swift index c667d45901..37fa2064c5 100644 --- a/submodules/TelegramCallsUI/Sources/CallController.swift +++ b/submodules/TelegramCallsUI/Sources/CallController.swift @@ -498,14 +498,12 @@ public final class CallController: ViewController { } static func openConferenceAddParticipant(context: AccountContext, disablePeerIds: [EnginePeer.Id], shareLink: (() -> Void)?, completion: @escaping ([(id: EnginePeer.Id, isVideo: Bool)]) -> Void) -> ViewController { - //TODO:localize let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme) var options: [ContactListAdditionalOption] = [] var openShareLinkImpl: (() -> Void)? if shareLink != nil { - //TODO:localize - options.append(ContactListAdditionalOption(title: "Share Call Link", icon: .generic(UIImage(bundleImageName: "Contact List/LinkActionIcon")!), action: { + options.append(ContactListAdditionalOption(title: presentationData.strings.Call_ShareLink, icon: .generic(UIImage(bundleImageName: "Contact List/LinkActionIcon")!), action: { openShareLinkImpl?() }, clearHighlightAutomatically: false)) } @@ -515,8 +513,7 @@ public final class CallController: ViewController { updatedPresentationData: (initial: presentationData, signal: .single(presentationData)), mode: .generic, title: { strings in - //TODO:localize - return "Add Member" + return strings.Call_AddMemberTitle }, options: .single(options), displayCallIcons: true, diff --git a/submodules/TelegramCallsUI/Sources/PresentationCall.swift b/submodules/TelegramCallsUI/Sources/PresentationCall.swift index 41555bd8e4..c6b182ac82 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCall.swift @@ -765,8 +765,8 @@ public final class PresentationCallImpl: PresentationCall { self.localVideoEndpointId = nil self.remoteVideoEndpointId = nil - //TODO:localize - self.callKitIntegration?.updateCallIsConference(uuid: self.internalId, title: self.conferenceTitle ?? "Group Call") + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + self.callKitIntegration?.updateCallIsConference(uuid: self.internalId, title: self.conferenceTitle ?? presentationData.strings.Call_GenericGroupCallTitle) } func internal_markAsCanBeRemoved() { diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 08fb8bee08..781dda7de0 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -3568,8 +3568,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { let presentationData = self.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme) - //TODO:localize - var errorText = "An error occurred" + var errorText = presentationData.strings.Login_UnknownError switch error { case let .privacy(peer): if let peer { diff --git a/submodules/TelegramCallsUI/Sources/VideoChatEncryptionKeyComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatEncryptionKeyComponent.swift index 58d84e2d1b..02cfbd82ca 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatEncryptionKeyComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatEncryptionKeyComponent.swift @@ -614,11 +614,10 @@ final class VideoChatEncryptionKeyComponent: Component { ) } - //TODO:localize let collapsedTextSize = self.collapsedText.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "End-to-end encrypted", font: Font.semibold(12.0), textColor: component.theme.list.itemPrimaryTextColor)) + text: .plain(NSAttributedString(string: component.strings.VideoChat_EncryptionKeyLabel, font: Font.semibold(12.0), textColor: component.theme.list.itemPrimaryTextColor)) )), environment: {}, containerSize: CGSize(width: 1000.0, height: 1000.0) @@ -627,7 +626,7 @@ final class VideoChatEncryptionKeyComponent: Component { let expandedTextSize = self.expandedText.update( transition: .immediate, component: AnyComponent(BalancedTextComponent( - text: .plain(NSAttributedString(string: "These four emojis represent the call's encryption key. They must match for all participants and change when someone joins or leaves.", font: Font.regular(12.0), textColor: component.theme.list.itemPrimaryTextColor)), + text: .plain(NSAttributedString(string: component.strings.VideoChat_EncryptionKeyText, font: Font.regular(12.0), textColor: component.theme.list.itemPrimaryTextColor)), maximumNumberOfLines: 0, lineSpacing: 0.3 )), @@ -638,7 +637,7 @@ final class VideoChatEncryptionKeyComponent: Component { let expandedButtonTextSize = self.expandedButtonText.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "Close", font: Font.regular(17.0), textColor: component.theme.list.itemPrimaryTextColor)) + text: .plain(NSAttributedString(string: component.strings.VideoChat_EncryptionKeyDone, font: Font.regular(17.0), textColor: component.theme.list.itemPrimaryTextColor)) )), environment: {}, containerSize: CGSize(width: availableSize.width - expandedSideInset * 2.0, height: 1000.0) diff --git a/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift index de122e01a9..a354a1c543 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift @@ -1282,18 +1282,7 @@ final class VideoChatParticipantsComponent: Component { let invitedPeer = component.invitedPeers[i - self.listParticipants.count] participantPeerId = invitedPeer.peer.id - let subtitle: PeerListItemComponent.Subtitle - //TODO:localize - switch invitedPeer.state { - case .none: - subtitle = PeerListItemComponent.Subtitle(text: component.strings.VoiceChat_StatusInvited, color: .neutral) - case .connecting: - subtitle = PeerListItemComponent.Subtitle(text: "connecting...", color: .neutral) - case .requesting: - subtitle = PeerListItemComponent.Subtitle(text: "requesting...", color: .neutral) - case .ringing: - subtitle = PeerListItemComponent.Subtitle(text: "invited", color: .neutral) - } + let subtitle: PeerListItemComponent.Subtitle = PeerListItemComponent.Subtitle(text: component.strings.VoiceChat_StatusInvited, color: .neutral) let rightAccessoryComponent: AnyComponent = AnyComponent(VideoChatParticipantInvitedStatusComponent( theme: component.theme @@ -1861,11 +1850,10 @@ final class VideoChatParticipantsComponent: Component { let iconType: VideoChatListInviteComponent.Icon switch inviteOption.type { case let .invite(isMultiple): - //TODO:localize if isMultiple { inviteText = component.strings.VoiceChat_InviteMember } else { - inviteText = "Add Member" + inviteText = component.strings.VideoChat_InviteMember } iconType = .addUser case .shareLink: diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index 787ec7d39c..cc82d746a5 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -685,9 +685,8 @@ final class VideoChatScreenComponent: Component { if let result { switch result { case .linkCopied: - //TODO:localize let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with { $0 } - self.environment?.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: "Call link copied.", customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in + self.environment?.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: presentationData.strings.CallList_ToastCallLinkCopied_Text, customUndoText: presentationData.strings.CallList_ToastCallLinkCopied_Action, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in return false }), in: .current) case .openCall: @@ -2038,11 +2037,10 @@ final class VideoChatScreenComponent: Component { maxTitleWidth -= 110.0 } - //TODO:localize let titleSize = self.title.update( transition: transition, component: AnyComponent(VideoChatTitleComponent( - title: self.callState?.title ?? self.peer?.debugDisplayTitle ?? "Group Call", + title: self.callState?.title ?? self.peer?.debugDisplayTitle ?? environment.strings.VideoChat_GroupCallTitle, status: idleTitleStatusText, isRecording: self.callState?.recordingStartTimestamp != nil, strings: environment.strings, diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift index 1fd32412b3..4d97fef81e 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift @@ -129,7 +129,6 @@ extension VideoChatScreenComponent.View { if let participant { dismissController?() - //TODO:release if groupCall.invitePeer(participant.peer.id, isVideo: false) { let text: String if case let .channel(channel) = self.peer, case .broadcast = channel.info { @@ -242,7 +241,6 @@ extension VideoChatScreenComponent.View { } dismissController?() - //TODO:release if groupCall.invitePeer(peer.id, isVideo: false) { let text: String if case let .channel(channel) = self.peer, case .broadcast = channel.info { @@ -315,7 +313,6 @@ extension VideoChatScreenComponent.View { } dismissController?() - //TODO:release if groupCall.invitePeer(peer.id, isVideo: false) { let text: String if case let .channel(channel) = self.peer, case .broadcast = channel.info { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index 60cefcd87c..d3c7626b85 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -1256,7 +1256,6 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController { if let participant = participant { dismissController?() - //TODO:release if strongSelf.call.invitePeer(participant.peer.id, isVideo: false) { let text: String if let channel = strongSelf.peer as? TelegramChannel, case .broadcast = channel.info { @@ -1365,7 +1364,6 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController { } dismissController?() - //TODO:release if strongSelf.call.invitePeer(peer.id, isVideo: false) { let text: String if let channel = strongSelf.peer as? TelegramChannel, case .broadcast = channel.info { @@ -1434,7 +1432,6 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController { } dismissController?() - //TODO:release if strongSelf.call.invitePeer(peer.id, isVideo: false) { let text: String if let channel = strongSelf.peer as? TelegramChannel, case .broadcast = channel.info { diff --git a/submodules/TelegramCore/Sources/State/ConferenceCallE2EContext.swift b/submodules/TelegramCore/Sources/State/ConferenceCallE2EContext.swift index 31d81d6c05..07471dfedb 100644 --- a/submodules/TelegramCore/Sources/State/ConferenceCallE2EContext.swift +++ b/submodules/TelegramCore/Sources/State/ConferenceCallE2EContext.swift @@ -182,7 +182,6 @@ public final class ConferenceCallE2EContext { self.e2eEncryptionKeyHashValue.set(outEmoji.isEmpty ? nil : outEmoji) for outBlock in outBlocks { - //TODO:release queue let _ = self.engine.calls.sendConferenceCallBroadcast(callId: self.callId, accessHash: self.accessHash, block: outBlock).startStandalone() } } @@ -400,7 +399,6 @@ public final class ConferenceCallE2EContext { } func kickPeer(id: EnginePeer.Id) { - //TODO:release if !self.pendingKickPeers.contains(id) { self.pendingKickPeers.append(id) diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index b013f04d16..328d44f08a 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -616,9 +616,27 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } } attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor) - case .conferenceCall: - //TODO:localize - let titleString = "Group call" + case let .conferenceCall(conferenceCall): + var titleString: String + let incoming = message.flags.contains(.Incoming) + + let missedTimeout: Int32 = 30 + let currentTime = Int32(Date().timeIntervalSince1970) + + if conferenceCall.flags.contains(.isMissed) { + titleString = strings.Chat_CallMessage_DeclinedGroupCall + } else if message.timestamp < currentTime - missedTimeout { + titleString = strings.Chat_CallMessage_MissedGroupCall + } else if conferenceCall.duration != nil { + titleString = strings.Chat_CallMessage_CancelledGroupCall + } else { + if incoming { + titleString = strings.Chat_CallMessage_IncomingGroupCall + } else { + titleString = strings.Chat_CallMessage_OutgoingGroupCall + } + } + attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor) case let .groupPhoneCall(_, _, scheduleDate, duration): if let scheduleDate = scheduleDate { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageCallBubbleContentNode/Sources/ChatMessageCallBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageCallBubbleContentNode/Sources/ChatMessageCallBubbleContentNode.swift index 03136f8bde..2bebc550ab 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageCallBubbleContentNode/Sources/ChatMessageCallBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageCallBubbleContentNode/Sources/ChatMessageCallBubbleContentNode.swift @@ -153,8 +153,7 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode { callDuration = conferenceCall.duration if conferenceCall.otherParticipants.count > 0 { - //TODO:localize - peopleTextString = "\(conferenceCall.otherParticipants.count + 1) people" + peopleTextString = item.presentationData.strings.Chat_CallMessage_GroupCallParticipantCount(Int32(conferenceCall.otherParticipants.count + 1)) if let peer = item.message.author { peopleAvatars.append(peer) } @@ -165,9 +164,8 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode { } } - //TODO:localize let missedTimeout: Int32 - #if DEBUG + #if DEBUG && false missedTimeout = 5 #else missedTimeout = 30 @@ -175,16 +173,16 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode { let currentTime = Int32(Date().timeIntervalSince1970) if conferenceCall.flags.contains(.isMissed) { - titleString = "Declined Group Call" + titleString = item.presentationData.strings.Chat_CallMessage_DeclinedGroupCall } else if item.message.timestamp < currentTime - missedTimeout { - titleString = "Missed Group Call" + titleString = item.presentationData.strings.Chat_CallMessage_MissedGroupCall } else if conferenceCall.duration != nil { - titleString = "Cancelled Group Call" + titleString = item.presentationData.strings.Chat_CallMessage_CancelledGroupCall } else { if incoming { - titleString = "Incoming Group Call" + titleString = item.presentationData.strings.Chat_CallMessage_IncomingGroupCall } else { - titleString = "Outgoing Group Call" + titleString = item.presentationData.strings.Chat_CallMessage_OutgoingGroupCall } updateConferenceTimerEndTimeout = (item.message.timestamp + missedTimeout) - currentTime } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index bc905b9149..70025dff94 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -475,8 +475,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent text = nil entities = nil case "telegram_call": - //TODO:localize - actionTitle = "JOIN GROUP CALL" + actionTitle = item.presentationData.strings.Chat_ViewGroupCall default: break } diff --git a/submodules/TelegramUI/Components/JoinSubjectScreen/Sources/JoinSubjectScreen.swift b/submodules/TelegramUI/Components/JoinSubjectScreen/Sources/JoinSubjectScreen.swift index 5f03c035fe..042be113c0 100644 --- a/submodules/TelegramUI/Components/JoinSubjectScreen/Sources/JoinSubjectScreen.swift +++ b/submodules/TelegramUI/Components/JoinSubjectScreen/Sources/JoinSubjectScreen.swift @@ -490,7 +490,7 @@ private final class JoinSubjectScreenComponent: Component { contentHeight += 31.0 titleString = group.title - subtitleString = group.isPublic ? "public group" : "private group" + subtitleString = group.isPublic ? environment.strings.Invitation_PublicGroup : environment.strings.Invitation_PrivateGroup descriptionTextString = group.about previewPeers = group.members @@ -528,10 +528,9 @@ private final class JoinSubjectScreenComponent: Component { } contentHeight += peerAvatarSize.height + 21.0 case let .groupCall(groupCall): - //TODO:localize - titleString = "Group Call" + titleString = environment.strings.Invitation_GroupCall subtitleString = nil - descriptionTextString = "You are invited to join a group call." + descriptionTextString = environment.strings.Invitation_GroupCall_Text previewPeers = groupCall.members totalMemberCount = groupCall.totalMemberCount @@ -691,12 +690,11 @@ private final class JoinSubjectScreenComponent: Component { if !previewPeers.isEmpty { contentHeight += 11.0 - //TODO:localize let previewPeersString: String switch component.mode { case .group: if previewPeers.count == 1 { - previewPeersString = "**\(previewPeers[0].compactDisplayTitle)** already joined this group." + previewPeersString = environment.strings.Invitation_Group_AlreadyJoinedSingle(previewPeers[0].compactDisplayTitle).string } else { let firstPeers = previewPeers.prefix(upTo: 2) let peersTextArray = firstPeers.map { "**\($0.compactDisplayTitle)**" } @@ -717,14 +715,14 @@ private final class JoinSubjectScreenComponent: Component { } } if totalMemberCount > firstPeers.count { - previewPeersString = "\(peersText) and **\(totalMemberCount - firstPeers.count)** other people already joined this group." + previewPeersString = environment.strings.Invitation_Group_AlreadyJoinedMultipleWithCount(Int32(totalMemberCount - firstPeers.count)).replacingOccurrences(of: "{}", with: peersText) } else { - previewPeersString = "\(peersText) already joined this group." + previewPeersString = environment.strings.Invitation_Group_AlreadyJoinedMultiple(peersText).string } } case .groupCall: if previewPeers.count == 1 { - previewPeersString = "**\(previewPeers[0].compactDisplayTitle)** already joined this call." + previewPeersString = environment.strings.Invitation_GroupCall_AlreadyJoinedSingle(previewPeers[0].compactDisplayTitle).string } else { let firstPeers = previewPeers.prefix(upTo: 2) let peersTextArray = firstPeers.map { "**\($0.compactDisplayTitle)**" } @@ -745,9 +743,9 @@ private final class JoinSubjectScreenComponent: Component { } } if totalMemberCount > firstPeers.count { - previewPeersString = "\(peersText) and **\(totalMemberCount - firstPeers.count)** other people already joined this call." + previewPeersString = environment.strings.Invitation_GroupCall_AlreadyJoinedMultipleWithCount(Int32(totalMemberCount - firstPeers.count)).replacingOccurrences(of: "{}", with: peersText) } else { - previewPeersString = "\(peersText) already joined this call." + previewPeersString = environment.strings.Invitation_GroupCall_AlreadyJoinedMultiple(peersText).string } } } @@ -854,8 +852,7 @@ private final class JoinSubjectScreenComponent: Component { case .group: actionButtonTitle = environment.strings.Invitation_JoinGroup case .groupCall: - //TODO:localize - actionButtonTitle = "Join Group Call" + actionButtonTitle = environment.strings.Invitation_JoinGroupCall } let actionButtonSize = self.actionButton.update( transition: transition, diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 5184a47089..75b293b704 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -2164,14 +2164,18 @@ private func extractAccountManagerState(records: AccountRecordsView take(1) + |> deliverOnMainQueue).start(next: { sharedApplicationContext in + strings = sharedApplicationContext.sharedContext.currentPresentationData.with { $0.strings } + }) + + let displayTitle: String if let memberCountString = payloadJson["member_count"] as? String, let memberCount = Int(memberCountString) { - if memberCount == 1 { - displayTitle.append(" and 1 other") - } else { - displayTitle.append(" and \(memberCount) others") - } + displayTitle = strings.Call_IncomingGroupCallTitle_Multiple(Int32(memberCount)).replacingOccurrences(of: "{}", with: fromTitle) + } else { + displayTitle = strings.Call_IncomingGroupCallTitle_Single(fromTitle).string } callKitIntegration.reportIncomingCall( diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index f09798f46f..c89712de75 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -2932,8 +2932,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.context.sharedContext.openCreateGroupCallUI(context: self.context, peerIds: conferenceCall.otherParticipants, parentController: self) default: let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - //TODO:localize - self.present(textAlertController(context: self.context, title: nil, text: "An error occurred", actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + self.present(textAlertController(context: self.context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } }) }, longTap: { [weak self] action, params in diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift index 58fa17ca74..3a2d3bc9cd 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift @@ -131,11 +131,10 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { }, checkOptionTitle: nil) case let .groupCreation(isCall): if isCall { - //TODO:localize - placeholder = "Search for contacts or usernames" + placeholder = self.presentationData.strings.NewCall_SearchPlaceholder self.footerPanelNode = FooterPanelNode(theme: self.presentationData.theme, strings: self.presentationData.strings, action: { proceedImpl?() - }, checkOptionTitle: isCall ? "Call with video enabled" : nil) + }, checkOptionTitle: self.presentationData.strings.NewCall_VideoOption) } else { placeholder = self.presentationData.strings.Compose_TokenListPlaceholder self.footerPanelNode = nil @@ -483,15 +482,14 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { count = contactListNode.selectionState?.selectedPeerIndices.count ?? 0 } if case let .groupCreation(isCall) = self.mode, isCall { - //TODO:localize if count == 0 { // Don't set anything to prevent state update } else if count <= 1 { let callTitle: String if case let .contacts(contactListNode) = self.contentNode, let peer = contactListNode.selectedPeers.first, case let .peer(peer, _, _) = peer { - callTitle = "Call \(EnginePeer(peer).compactDisplayTitle)" + callTitle = self.presentationData.strings.NewCall_ActionCallSingle(EnginePeer(peer).compactDisplayTitle).string } else { - callTitle = "Call" + callTitle = self.presentationData.strings.NewCall_ActionCallMultiple } footerPanelNode.content = FooterPanelNode.Content(title: callTitle, badge: "") } else { diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index bcb6f012d2..b313960db0 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -407,8 +407,7 @@ func openResolvedUrlImpl( if case .chat = urlContext { elevatedLayout = false } - //TODO:localize - present(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: "This link is no longer active"), elevatedLayout: elevatedLayout, animateInAsReplacement: false, action: { _ in + present(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: presentationData.strings.Chat_ToastCallLinkExpired_Text), elevatedLayout: elevatedLayout, animateInAsReplacement: false, action: { _ in return true }), nil) }) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index d3bd0f15bf..6103230f8d 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1962,10 +1962,11 @@ public final class SharedAccountContextImpl: SharedAccountContext { return } + let isVideo = controller?.isCallVideoOptionSelected ?? false + if peerIds.count == 1 { - //TODO:release isVideo controller?.dismiss() - self.performCall(context: context, parentController: parentController, peerId: peerIds[0], isVideo: false, began: { + self.performCall(context: context, parentController: parentController, peerId: peerIds[0], isVideo: isVideo, began: { let _ = (context.sharedContext.hasOngoingCall.get() |> filter { $0 } |> timeout(1.0, queue: Queue.mainQueue(), alternate: .single(true)) @@ -1980,7 +1981,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { }) }) } else { - self.createGroupCall(context: context, parentController: parentController, peerIds: peerIds, completion: { + self.createGroupCall(context: context, parentController: parentController, peerIds: peerIds, isVideo: isVideo, completion: { controller?.dismiss() }) } @@ -2011,7 +2012,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { }) } - private func createGroupCall(context: AccountContext, parentController: ViewController, peerIds: [EnginePeer.Id], completion: (() -> Void)? = nil) { + private func createGroupCall(context: AccountContext, parentController: ViewController, peerIds: [EnginePeer.Id], isVideo: Bool, completion: (() -> Void)? = nil) { parentController.view.endEditing(true) var cancelImpl: (() -> Void)? @@ -2057,7 +2058,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { isStream: false ), reference: .id(id: call.callInfo.id, accessHash: call.callInfo.accessHash), - beginWithVideo: false, + beginWithVideo: isVideo, invitePeerIds: peerIds ) completion?() @@ -2079,9 +2080,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { if let result { switch result { case .linkCopied: - //TODO:localize let presentationData = context.sharedContext.currentPresentationData.with { $0 } - parentController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: "Call link copied.", customUndoText: "View Call", timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in + parentController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: presentationData.strings.CallList_ToastCallLinkCopied_Text, customUndoText: presentationData.strings.CallList_ToastCallLinkCopied_Action, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in if case .undo = action { openCall() }