diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 0f76d386ed..8eb9165453 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12779,3 +12779,29 @@ Sorry for the inconvenience."; "ChatList.SubscriptionsLowBalance.Multiple.Title" = "%@ for your subscriptions"; "ChatList.SubscriptionsLowBalance.Multiple.Text" = "Insufficient funds to cover your subscriptions."; + +"ChatList.Search.SectionApps" = "APPS"; + +"Channel.ShowAuthors" = "Show Authors' Profiles"; +"Channel.ShowAuthorsFooter" = "Add names and photos of admins to the messages they post, linking to their profiles."; + +"SendStarReactions.Title" = "React with Stars"; +"SendStarReactions.Balance" = "Balance"; +"SendStarReactions.UserLabelAnonymous" = "Anonymous"; +"SendStarReactions.SliderTop" = "TOP"; +"SendStarReactions.TextSentStars_1" = "You sent **1** star to support this post."; +"SendStarReactions.TextSentStars_any" = "You sent **%d** stars to support this post."; +"SendStarReactions.TextGeneric" = "Choose how many stars you want to send to **%@** to support this post."; +"SendStarReactions.SectionTop" = "Top Senders"; +"SendStarReactions.ShowMyselfInTop" = "Show me in Top Senders"; +"SendStarReactions.SendButtonTitle" = "Send # %@"; +"SendStarReactions.TermsOfServiceFooter" = "By sending Stars you agree to the [Terms of Service](https://telegram.org/privacy)"; + +"PeerInfo.AllowedReactions.StarReactions" = "Enable Paid Reactions"; +"PeerInfo.AllowedReactions.StarReactionsFooter" = "Switch this on to let your subscribers set paid reactions with Telegram Stars, which you will be able to withdraw later as TON. [Learn More >](https://telegram.org/privacy)"; + +"Chat.ToastStarsSent.Title_1" = "Star sent!"; +"Chat.ToastStarsSent.Title_any" = "Stars sent!"; +"Chat.ToastStarsSent.Text" = "You have reacted with %1$@ %2$@."; +"Chat.ToastStarsSent.TextStarAmount_1" = "star"; +"Chat.ToastStarsSent.TextStarAmount_any" = "stars"; diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 4a8c2fec32..5f4b035ec6 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -742,8 +742,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { if case .channels = key { headerType = .channels } else if case .apps = key { - //TODO:localize - headerType = .text("APPS", AnyHashable("apps")) + headerType = .text(strings.ChatList_Search_SectionApps, AnyHashable("apps")) } else { if filter.contains(.onlyGroups) { headerType = .chats diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift index e43fa823b3..0e97f1f4f4 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift @@ -496,10 +496,9 @@ private func channelAdminsControllerEntries(presentationData: PresentationData, if !isGroup && peer.hasPermission(.sendSomething) { entries.append(.signMessages(presentationData.theme, presentationData.strings.Channel_SignMessages, signMessagesEnabled, showAuthorProfilesEnabled)) - //TODO:localize - entries.append(.showAuthorProfiles(presentationData.theme, "Show Authors' Profiles", showAuthorProfilesEnabled, signMessagesEnabled)) + entries.append(.showAuthorProfiles(presentationData.theme, presentationData.strings.Channel_ShowAuthors, showAuthorProfilesEnabled, signMessagesEnabled)) if signMessagesEnabled { - entries.append(.signMessagesInfo(presentationData.theme, "Add names and photos of admins to the messages they post, linking to their profiles.")) + entries.append(.signMessagesInfo(presentationData.theme, presentationData.strings.Channel_ShowAuthorsFooter)) } } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index 2055bbcf12..2be9d0c8c5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -754,7 +754,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } } strongSelf.textAccessibilityOverlayNode.frame = textFrame - //TODO:localize + //TODO:release //strongSelf.textAccessibilityOverlayNode.cachedLayout = textLayout strongSelf.updateIsTranslating(isTranslating) diff --git a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift index e7dfa2d4c5..456f1e8a3f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift @@ -62,10 +62,9 @@ private final class BalanceComponent: CombinedComponent { return { context in var size = CGSize(width: 0.0, height: 0.0) - //TODO:localize let title = title.update( component: MultilineTextComponent( - text: .plain(NSAttributedString(string: "Balance", font: Font.regular(14.0), textColor: context.component.theme.list.itemPrimaryTextColor)) + text: .plain(NSAttributedString(string: context.component.strings.SendStarReactions_Balance, font: Font.regular(14.0), textColor: context.component.theme.list.itemPrimaryTextColor)) ), availableSize: context.availableSize, transition: .immediate @@ -566,8 +565,7 @@ private final class PeerComponent: Component { if let peer = component.peer { peerTitle = peer.compactDisplayTitle } else { - //TODO:localize - peerTitle = "Anonymous" + peerTitle = component.strings.SendStarReactions_UserLabelAnonymous } let titleSize = self.title.update( @@ -601,15 +599,18 @@ private final class PeerComponent: Component { private final class SliderBackgroundComponent: Component { let theme: PresentationTheme + let strings: PresentationStrings let value: CGFloat let topCutoff: CGFloat? init( theme: PresentationTheme, + strings: PresentationStrings, value: CGFloat, topCutoff: CGFloat? ) { self.theme = theme + self.strings = strings self.value = value self.topCutoff = topCutoff } @@ -618,6 +619,9 @@ private final class SliderBackgroundComponent: Component { if lhs.theme !== rhs.theme { return false } + if lhs.strings !== rhs.strings { + return false + } if lhs.value != rhs.value { return false } @@ -739,11 +743,10 @@ private final class SliderBackgroundComponent: Component { topLineFrameTransition.setFrame(layer: self.topBackgroundLine, frame: topLineFrame) topLineAlphaTransition.setAlpha(layer: self.topBackgroundLine, alpha: topLineAlpha) - //TODO:localize let topTextSize = self.topForegroundText.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "TOP", font: Font.semibold(15.0), textColor: UIColor(white: 1.0, alpha: 0.4))) + text: .plain(NSAttributedString(string: component.strings.SendStarReactions_SliderTop, font: Font.semibold(15.0), textColor: UIColor(white: 1.0, alpha: 0.4))) )), environment: {}, containerSize: CGSize(width: availableSize.width, height: 100.0) @@ -751,7 +754,7 @@ private final class SliderBackgroundComponent: Component { let _ = self.topBackgroundText.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "TOP", font: Font.semibold(15.0), textColor: component.theme.overallDarkAppearance ? UIColor(white: 1.0, alpha: 0.22) : UIColor(white: 0.0, alpha: 0.2))) + text: .plain(NSAttributedString(string: component.strings.SendStarReactions_SliderTop, font: Font.semibold(15.0), textColor: component.theme.overallDarkAppearance ? UIColor(white: 1.0, alpha: 0.22) : UIColor(white: 0.0, alpha: 0.2))) )), environment: {}, containerSize: CGSize(width: availableSize.width, height: 100.0) @@ -1256,6 +1259,7 @@ private final class ChatSendStarsScreenComponent: Component { transition: transition, component: AnyComponent(SliderBackgroundComponent( theme: environment.theme, + strings: environment.strings, value: progressFraction, topCutoff: topCutoffFraction )), @@ -1388,7 +1392,7 @@ private final class ChatSendStarsScreenComponent: Component { let titleSize = title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "React with Stars", font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor)) + text: .plain(NSAttributedString(string: environment.strings.SendStarReactions_Title, font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor)) )), environment: {}, containerSize: CGSize(width: availableSize.width - leftButtonFrame.maxX * 2.0, height: 100.0) @@ -1406,9 +1410,9 @@ private final class ChatSendStarsScreenComponent: Component { let text: String if let currentSentAmount = component.currentSentAmount { - text = "You sent **\(currentSentAmount)** stars to support this post." + text = environment.strings.SendStarReactions_TextSentStars(Int32(currentSentAmount)) } else { - text = "Choose how many stars you want to send to **\(component.peer.debugDisplayTitle)** to support this post." + text = environment.strings.SendStarReactions_TextGeneric(component.peer.debugDisplayTitle).string } let body = MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor) @@ -1482,11 +1486,10 @@ private final class ChatSendStarsScreenComponent: Component { topPeersLeftSeparator.backgroundColor = environment.theme.list.itemPlainSeparatorColor.cgColor topPeersRightSeparator.backgroundColor = environment.theme.list.itemPlainSeparatorColor.cgColor - //TODO:localize let topPeersTitleSize = topPeersTitle.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "Top Senders", font: Font.semibold(15.0), textColor: .white)) + text: .plain(NSAttributedString(string: environment.strings.SendStarReactions_SectionTop, font: Font.semibold(15.0), textColor: .white)) )), environment: {}, containerSize: CGSize(width: 300.0, height: 100.0) @@ -1675,7 +1678,7 @@ private final class ChatSendStarsScreenComponent: Component { selected: !self.isAnonymous ))), AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "Show me in Top Senders", font: Font.regular(16.0), textColor: environment.theme.list.itemPrimaryTextColor)) + text: .plain(NSAttributedString(string: environment.strings.SendStarReactions_ShowMyselfInTop, font: Font.regular(16.0), textColor: environment.theme.list.itemPrimaryTextColor)) ))) ], spacing: 10.0 @@ -1720,7 +1723,7 @@ private final class ChatSendStarsScreenComponent: Component { self.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: .white)!, environment.theme) } - let buttonString = "Send # \(self.amount)" + let buttonString = environment.strings.SendStarReactions_SendButtonTitle("\(self.amount)").string let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: .white, paragraphAlignment: .center) if let range = buttonAttributedString.string.range(of: "#"), let starImage = self.cachedStarImage?.0 { buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string)) @@ -1801,7 +1804,7 @@ private final class ChatSendStarsScreenComponent: Component { let buttonDescriptionTextSize = self.buttonDescriptionText.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .markdown(text: "By sending Stars you agree to the [Terms of Service]()", attributes: MarkdownAttributes( + text: .markdown(text: environment.strings.SendStarReactions_TermsOfServiceFooter, attributes: MarkdownAttributes( body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemSecondaryTextColor), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.itemSecondaryTextColor), link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemAccentColor), diff --git a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift index 825c9e4dd8..a8bbe1b016 100644 --- a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift +++ b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift @@ -907,8 +907,7 @@ final class PeerAllowedReactionsScreenComponent: Component { self.paidReactionsSection = paidReactionsSection } - //TODO:localize - let parsedString = parseMarkdownIntoAttributedString("Switch this on to let your subscribers set paid reactions with Telegram Stars, which you will be able to withdraw later as TON. [Learn More >](https://telegram.org/privacy)", attributes: MarkdownAttributes( + let parsedString = parseMarkdownIntoAttributedString(environment.strings.PeerInfo_AllowedReactions_StarReactionsFooter, attributes: MarkdownAttributes( body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.freeTextColor), link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemAccentColor), @@ -925,7 +924,6 @@ final class PeerAllowedReactionsScreenComponent: Component { paidReactionsFooterText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: paidReactionsFooterText.string)) } - //TODO:localize let paidReactionsSectionSize = paidReactionsSection.update( transition: transition, component: AnyComponent(ListSectionComponent( @@ -953,7 +951,7 @@ final class PeerAllowedReactionsScreenComponent: Component { items: [ AnyComponentWithIdentity(id: 0, component: AnyComponent(ListSwitchItemComponent( theme: environment.theme, - title: "Enable Paid Reactions", + title: environment.strings.PeerInfo_AllowedReactions_StarReactions, value: self.areStarsReactionsEnabled, valueUpdated: { [weak self] value in guard let self, let component = self.component else { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index 71a00344a7..178920de70 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -1655,7 +1655,7 @@ private final class StoryContainerScreenComponent: Component { } if case let .user(user) = slice.peer, user.botInfo != nil { - //TODO:localize + //TODO:release let _ = component.context.engine.messages.deleteBotPreviews(peerId: slice.peer.id, language: nil, media: [slice.item.storyItem.media._asMedia()]).startStandalone() } else { let _ = component.context.engine.messages.deleteStories(peerId: slice.peer.id, ids: [slice.item.storyItem.id]).startStandalone() diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift index da1d1899b0..ed92d13607 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift @@ -481,21 +481,14 @@ extension ChatControllerImpl { self.currentSendStarsUndoCount = count } - //TODO:localize - let title: String - if self.currentSendStarsUndoCount == 1 { - title = "Star sent!" - } else { - title = "Stars sent!" - } + let title: String = self.presentationData.strings.Chat_ToastStarsSent_Title(Int32(self.currentSendStarsUndoCount)) - var textItems: [AnimatedTextComponent.Item] = [] - textItems.append(AnimatedTextComponent.Item(id: AnyHashable(0), isUnbreakable: true, content: .text("You have reacted with "))) - textItems.append(AnimatedTextComponent.Item(id: AnyHashable(1), content: .number(self.currentSendStarsUndoCount, minDigits: 1))) - textItems.append(AnimatedTextComponent.Item(id: AnyHashable(2), isUnbreakable: true, content: .text(self.currentSendStarsUndoCount == 1 ? " star." : " stars."))) + let textItems = extractAnimatedTextString(string: self.presentationData.strings.Chat_ToastStarsSent_Text("", ""), id: "text", mapping: [ + 0: .number(self.currentSendStarsUndoCount, minDigits: 1), + 1: .text(self.presentationData.strings.Chat_ToastStarsSent_TextStarAmount(Int32(self.currentSendStarsUndoCount))) + ]) self.currentSendStarsUndoMessageId = messageId - //TODO:localize if let current = self.currentSendStarsUndoController { current.content = .starsSent(context: self.context, title: title, text: textItems) } else { @@ -513,3 +506,31 @@ extension ChatControllerImpl { } } } + +private func extractAnimatedTextString(string: PresentationStrings.FormattedString, id: String, mapping: [Int: AnimatedTextComponent.Item.Content]) -> [AnimatedTextComponent.Item] { + var textItems: [AnimatedTextComponent.Item] = [] + + var previousIndex = 0 + let nsString = string.string as NSString + for range in string.ranges.sorted(by: { $0.range.lowerBound < $1.range.lowerBound }) { + if range.range.lowerBound > previousIndex { + textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_text_before_\(range.index)"), isUnbreakable: true, content: .text(nsString.substring(with: NSRange(location: previousIndex, length: range.range.lowerBound - previousIndex))))) + } + if let value = mapping[range.index] { + let isUnbreakable: Bool + switch value { + case .text: + isUnbreakable = true + case .number: + isUnbreakable = false + } + textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_item_\(range.index)"), isUnbreakable: isUnbreakable, content: value)) + } + previousIndex = range.range.upperBound + } + if nsString.length > previousIndex { + textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_text_end"), isUnbreakable: true, content: .text(nsString.substring(with: NSRange(location: previousIndex, length: nsString.length - previousIndex))))) + } + + return textItems +} diff --git a/submodules/TelegramUI/Sources/PollResultsController.swift b/submodules/TelegramUI/Sources/PollResultsController.swift index 071e764638..ec90f3fd67 100644 --- a/submodules/TelegramUI/Sources/PollResultsController.swift +++ b/submodules/TelegramUI/Sources/PollResultsController.swift @@ -223,7 +223,7 @@ private enum PollResultsEntry: ItemListNodeEntry { return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .solutionText(text, entities): let _ = entities - //TODO:localize + //TODO:release return ItemListMultilineTextItem(presentationData: presentationData, text: text, enabledEntityTypes: [], sectionId: self.section, style: .blocks) case let .optionPeer(optionId, _, peer, optionText, optionTextEntities, optionAdditionalText, optionCount, optionExpanded, opaqueIdentifier, shimmeringAlternation, isFirstInOption): let font = Font.regular(13.0)