From 96089108cabae924b80326d3aa147aeddc857078 Mon Sep 17 00:00:00 2001 From: isaac <> Date: Wed, 29 Apr 2026 16:55:37 +0400 Subject: [PATCH 1/4] Fixes --- .../State/AccountStateManagementUtils.swift | 9 ++++++ .../Metal/VoiceChatActionButtonShaders.metal | 9 ------ .../Sources/TextProcessingScreen.swift | 32 +++++++++---------- .../Sources/TextStyleEditScreen.swift | 2 +- 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index adc7c8fe68..d82675de53 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -2806,6 +2806,15 @@ func extractEmojiFileIds(message: StoreMessage, fileIds: inout Set) { } } } + for media in message.media { + if let media = media as? TelegramMediaWebpage, case let .Loaded(content) = media.content { + for attribute in content.attributes { + if case let .aiTextStyle(aiTextStyle) = attribute { + fileIds.insert(aiTextStyle.emojiFileId) + } + } + } + } } private func messagesFromOperations(state: AccountMutableState) -> [StoreMessage] { diff --git a/submodules/TelegramUI/Components/Calls/VoiceChatActionButton/Metal/VoiceChatActionButtonShaders.metal b/submodules/TelegramUI/Components/Calls/VoiceChatActionButton/Metal/VoiceChatActionButtonShaders.metal index 8834ab1672..0ccd808798 100644 --- a/submodules/TelegramUI/Components/Calls/VoiceChatActionButton/Metal/VoiceChatActionButtonShaders.metal +++ b/submodules/TelegramUI/Components/Calls/VoiceChatActionButton/Metal/VoiceChatActionButtonShaders.metal @@ -7,15 +7,6 @@ struct Rectangle { float2 size; }; -constant static float2 quadVertices[6] = { - float2(0.0, 0.0), - float2(1.0, 0.0), - float2(0.0, 1.0), - float2(1.0, 0.0), - float2(0.0, 1.0), - float2(1.0, 1.0) -}; - struct QuadVertexOut { float4 position [[position]]; float2 uv; diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift index acbec41187..2a17a4afbe 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift @@ -369,7 +369,7 @@ final class TextProcessingContentComponent: Component { )) } items.append(.action(ContextMenuActionItem( - text: "Share Style", + text: "Share Style", //TODO:localize icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, @@ -382,23 +382,21 @@ final class TextProcessingContentComponent: Component { }) }) )) - if style.isAuthor { - items.append(.action(ContextMenuActionItem( - text: "Delete Style", - textColor: .destructive, - icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) - }, - action: { [weak self] c, _ in - c?.dismiss(completion: { [weak self] in - guard let self else { - return - } - self.requestDeleteStyle(id: id) - }) + items.append(.action(ContextMenuActionItem( + text: "Delete Style", + textColor: .destructive, + icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + }, + action: { [weak self] c, _ in + c?.dismiss(completion: { [weak self] in + guard let self else { + return + } + self.requestDeleteStyle(id: id) }) - )) - } + }) + )) final class ContextExtractedContentSourceImpl: ContextExtractedContentSource { let keepInPlace: Bool = false diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextStyleEditScreen.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextStyleEditScreen.swift index 3c1fbb09f3..88e0e2fa9d 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextStyleEditScreen.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextStyleEditScreen.swift @@ -889,7 +889,7 @@ public class TextStyleEditScreen: ViewControllerComponentContainer { var initialEmojiFile: TelegramMediaFile? if case let .edit(style) = mode, case let .custom(style) = style.content, let emojiFileId = style.emojiFileId { - initialEmojiFile = await context.engine.stickers.resolveInlineStickers(fileIds: [emojiFileId]).get()[emojiFileId] + initialEmojiFile = await context.engine.stickers.resolveInlineStickersLocal(fileIds: [emojiFileId]).get()[emojiFileId] } super.init( From 6eb201465096df117b29d9dc43c1c9805f3ca279 Mon Sep 17 00:00:00 2001 From: isaac <> Date: Wed, 29 Apr 2026 18:45:26 +0400 Subject: [PATCH 2/4] Fix web --- .../WebUI/Sources/WebAppController.swift | 115 +++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index a72f287f7a..90202b3f4c 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -1601,7 +1601,7 @@ public final class WebAppController: ViewController, AttachmentContainable { self.controller?._isPanGestureEnabled = isPanGestureEnabled } case "web_app_share_to_story": - if let json, let mediaUrl = json["media_url"] as? String { + if let json, let mediaUrl = json["media_url"] as? String, isAllowedBotMediaUrl(mediaUrl) { let text = json["text"] as? String let link = json["widget_link"] as? [String: Any] @@ -4309,3 +4309,116 @@ private struct WebAppConfiguration { } } } + +private func isAllowedBotMediaUrl(_ urlString: String) -> Bool { + guard let escaped = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), + let url = URL(string: escaped) else { + return false + } + guard url.scheme?.lowercased() == "https" else { + return false + } + if url.user != nil || url.password != nil { + return false + } + guard var host = url.host?.lowercased(), !host.isEmpty else { + return false + } + if host.hasPrefix("[") && host.hasSuffix("]") { + host = String(host.dropFirst().dropLast()) + } + + // Strict canonical dotted-decimal IPv4 (4 octets, no leading zeros, each 0-255). + // Do NOT use inet_pton here: Darwin's inet_pton accepts "0177.0.0.1" as + // decimal 177.0.0.1, but getaddrinfo (used by URLSession) interprets the + // same string as octal 127.0.0.1 — the divergence is a loopback bypass. + if let v4Bytes = parseCanonicalIPv4(host) { + return isPublicIPv4(v4Bytes) + } + + // IPv6 only — host must contain ":" so we don't accidentally hand a + // numeric-looking hostname to inet_pton. + if host.contains(":") { + var v6 = in6_addr() + if host.withCString({ inet_pton(AF_INET6, $0, &v6) }) == 1 { + let bytes = withUnsafeBytes(of: &v6) { ptr -> [UInt8] in + return Array(ptr) + } + return isPublicIPv6(bytes) + } + return false + } + + // Strict DNS-name validation. Anything that doesn't look like a real + // FQDN is rejected — this catches non-canonical numeric IP forms + // (decimal-32 like "2130706433", octal like "0177.0.0.1", hex like + // "0x7f.0.0.1", short forms like "127.1") that the OS resolver may + // still treat as 127.0.0.1 even when inet_pton would accept them as + // a different value or reject outright. + let labels = host.split(separator: ".", omittingEmptySubsequences: false) + guard labels.count >= 2 else { return false } + for label in labels { + guard !label.isEmpty, label.count <= 63 else { return false } + if label.first == "-" || label.last == "-" { return false } + for ch in label { + guard ch.isASCII else { return false } + if !(ch.isLetter || ch.isNumber || ch == "-") { return false } + } + } + guard let tld = labels.last, tld.count >= 2, tld.contains(where: { $0.isLetter }) else { + return false + } + + if host == "localhost" || host.hasSuffix(".localhost") || host.hasSuffix(".local") { + return false + } + return true +} + +private func parseCanonicalIPv4(_ host: String) -> [UInt8]? { + let parts = host.split(separator: ".", omittingEmptySubsequences: false) + guard parts.count == 4 else { return nil } + var bytes: [UInt8] = [] + bytes.reserveCapacity(4) + for part in parts { + guard !part.isEmpty, part.count <= 3 else { return nil } + if part.count > 1 && part.first == "0" { return nil } // no leading zeros (octal-spoof) + guard part.allSatisfy({ $0.isASCII && $0.isNumber }) else { return nil } + guard let value = UInt8(part) else { return nil } // also caps at 255 + bytes.append(value) + } + return bytes +} + +private func isPublicIPv4(_ bytes: [UInt8]) -> Bool { + guard bytes.count == 4 else { return false } + let a = bytes[0] + let b = bytes[1] + if a == 0 { return false } // 0.0.0.0/8 + if a == 10 { return false } // 10.0.0.0/8 + if a == 127 { return false } // 127.0.0.0/8 loopback + if a == 169 && b == 254 { return false } // 169.254.0.0/16 link-local + if a == 172 && (b & 0xf0) == 16 { return false } // 172.16.0.0/12 + if a == 192 && b == 168 { return false } // 192.168.0.0/16 + if a == 100 && (b & 0xc0) == 64 { return false } // 100.64.0.0/10 CGNAT + if a >= 224 { return false } // multicast + reserved + 255.255.255.255 + return true +} + +private func isPublicIPv6(_ bytes: [UInt8]) -> Bool { + guard bytes.count == 16 else { return false } + if bytes.allSatisfy({ $0 == 0 }) { return false } // :: + let loopback: [UInt8] = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1] + if bytes == loopback { return false } // ::1 + if bytes[0] == 0xff { return false } // ff00::/8 multicast + if bytes[0] == 0xfe && (bytes[1] & 0xc0) == 0x80 { return false } // fe80::/10 link-local + if (bytes[0] & 0xfe) == 0xfc { return false } // fc00::/7 unique-local + let v4MappedPrefix: [UInt8] = [0,0,0,0,0,0,0,0,0,0,0xff,0xff] + if Array(bytes.prefix(12)) == v4MappedPrefix { // ::ffff:a.b.c.d + return isPublicIPv4(Array(bytes.suffix(4))) + } + if Array(bytes.prefix(12)).allSatisfy({ $0 == 0 }) { // ::a.b.c.d (deprecated) + return isPublicIPv4(Array(bytes.suffix(4))) + } + return true +} From 53ed837123d3598d55c805fc7ecc1a5fbafa82d9 Mon Sep 17 00:00:00 2001 From: isaac <> Date: Wed, 29 Apr 2026 21:22:51 +0400 Subject: [PATCH 3/4] Filter draft events --- .../Postbox/Sources/MessageHistoryView.swift | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/submodules/Postbox/Sources/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index f9e4792913..c3f80e1ef8 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -1069,28 +1069,30 @@ final class MutableMessageHistoryView: MutablePostboxView { } } - switch self.peerIds { - case let .single(peerId, threadId): - let location = PeerAndThreadId(peerId: peerId, threadId: threadId) - if let typingDraftUpdate = transaction.updatedTypingDrafts[location] { - if let typingDraft = typingDraftUpdate.value, self.namespaces.contains(typingDraft.namespace) { - self.typingDraft = self.renderTypingDraft(postbox: postbox, typingDraft: typingDraft) - } else { - self.typingDraft = nil + if self.tag == nil { + switch self.peerIds { + case let .single(peerId, threadId): + let location = PeerAndThreadId(peerId: peerId, threadId: threadId) + if let typingDraftUpdate = transaction.updatedTypingDrafts[location] { + if let typingDraft = typingDraftUpdate.value, self.namespaces.contains(typingDraft.namespace) { + self.typingDraft = self.renderTypingDraft(postbox: postbox, typingDraft: typingDraft) + } else { + self.typingDraft = nil + } + hasChanges = true } - hasChanges = true + case .external: + break + case .associated: + break } - case .external: - break - case .associated: - break } return hasChanges } private func reloadTypingDraft(postbox: PostboxImpl) { - guard case let .single(peerId, threadId) = self.peerIds else { + guard case let .single(peerId, threadId) = self.peerIds, self.tag == nil else { self.typingDraft = nil return } From 301be02a89b23ebd641a7ef9fabecad53c0046b7 Mon Sep 17 00:00:00 2001 From: isaac <> Date: Wed, 29 Apr 2026 21:23:36 +0400 Subject: [PATCH 4/4] Update localization --- .../Telegram-iOS/en.lproj/Localizable.strings | 41 +++++++++++++++++++ .../Sources/VideoChatScreen.swift | 5 +-- .../Sources/TextProcessingScreen.swift | 28 ++++++------- ...extProcessingStyleSelectionComponent.swift | 3 +- ...tProcessingTranslateContentComponent.swift | 24 +++++------ .../Sources/TextStyleEditScreen.swift | 22 +++++----- .../Sources/ChatControllerContentData.swift | 3 +- .../TelegramUI/Sources/OpenChatMessage.swift | 3 +- .../TelegramUI/Sources/OpenResolvedUrl.swift | 3 +- 9 files changed, 80 insertions(+), 52 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index b7e3a23bc7..ba567e70b0 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -16155,6 +16155,41 @@ Error: %8$@"; "TextProcessing.ResultBadge" = "Result"; "TextProcessing.Translate.LanguageStyle" = "%1$@ (%2$@)"; "TextProcessing.StyleTooltip" = "Select Style"; +"TextProcessing.AlertCreatorDeleteStyle.Title" = "Delete Style"; +"TextProcessing.AlertCreatorDeleteStyle.Text" = "Are you sure you want to delete this style? It will be removed for everyone who installed it."; +"TextProcessing.AlertDeleteStyle.Title" = "Delete Style"; +"TextProcessing.AlertDeleteStyle.Text" = "Are you sure you want to delete this style?"; +"TextProcessing.StyleMenu.Edit" = "Edit Style"; +"TextProcessing.StyleMenu.Share" = "Share Style"; +"TextProcessing.StyleMenu.Delete" = "Delete Style"; +"TextProcessing.StyleMenu.ButtonClose" = "Close"; +"TextProcessing.StyleMenu.ButtonAdd" = "Add Style"; +"TextProcessing.StylePreview.ExampleHeader" = "EXAMPLE"; +"TextProcessing.StylePreview.ExampleHeaderRefresh" = "ANOTHER EXAMPLE"; +"TextProcessing.StylePreview.Subtitle" = "Add this style to instantly\nrewrite your messages."; +"TextProcessing.StylePreview.Before" = "Before"; +"TextProcessing.StylePreview.After" = "After"; +"TextProcessing.AlertTooManyStyles.Title" = "Too Many Styles"; +"TextProcessing.AlertTooManyStyles.Text" = "Please delete some of your saved styles to create a new one."; +"TextProcessing.ToastStyleCreated.Title" = "%@ style created!"; +"TextProcessing.ToastStyleCreated.Text" = "Press and hold a style to edit or share the link."; +"TextProcessing.StyleList.Add" = "Add Style"; +"TextProcessing.StyleFooterAuthor" = "Style by [%@]()"; +"TextProcessing.StyleFooterUserCount_1" = "Used by 1 person"; +"TextProcessing.StyleFooterUserCount_any" = "Used by %d people"; +"TextProcessing.StyleFooterCreatedByFormat" = "%1$@. %2$@"; +"TextProcessing.StyleFooterCreatedBy" = "Created by [%@]()"; +"TextProcessing.StyleFooterCreatedBySimpleFormat" = "%@."; +"TextProcessing.EditStyle.NamePlaceholder" = "Style Name (for example, \"Pirate\")"; +"TextProcessing.EditStyle.TextPlaceholder" = "Instructions (for example, \"Write like a swashbuckling pirate. Use arr, ye, matey, and talk about treasure, the sea, and rum\")"; +"TextProcessing.EditStyle.TitleCreate" = "New Style"; +"TextProcessing.EditStyle.TitleEdit" = "Edit Style"; +"TextProcessing.EditStyle.ActionCreate" = "Create"; +"TextProcessing.EditStyle.ActionEdit" = "Save"; +"TextProcessing.EditStyle.Delete" = "Delete Style"; +"TextProcessing.EditStyle.AddLink" = "Add a link to my account"; +"TextProcessing.ToastStyleAdded.Title" = "Style Added"; +"TextProcessing.ToastStyleAdded.Text" = "Tap 'AI' → '%@' when typing your next long message."; "Bot.AlertCanNotCreateBots" = "%@ can't manage other bots."; @@ -16256,3 +16291,9 @@ Error: %8$@"; "Settings.ChatAutomation" = "Chat Automation"; "Settings.ChatAutomationInfo" = "Add a bot to reply to messages on your behalf."; "Settings.ChatAutomationOff" = "Off"; + +"Chat.SavedMessagesStatusViewAsChats" = "Tap to view as chats"; +"Chat.ToastVoiceMessageDeviceMuted" = "Device is muted."; + +"VideoChat.StatusPeerJoined" = "%@ joined"; +"VideoChat.StatusPeerLeft" = "%@ left"; diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index cfb47b39b3..7cc99459e5 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -1911,11 +1911,10 @@ final class VideoChatScreenComponent: Component { } } } else { - //TODO:localized if event.joined { - self.lastTitleEvent = "\(event.peer.compactDisplayTitle) joined" + self.lastTitleEvent = environment.strings.VideoChat_StatusPeerJoined(event.peer.compactDisplayTitle).string } else { - self.lastTitleEvent = "\(event.peer.compactDisplayTitle) left" + self.lastTitleEvent = environment.strings.VideoChat_StatusPeerLeft(event.peer.compactDisplayTitle).string } if !self.isUpdating { self.state?.updated(transition: .spring(duration: 0.4)) diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift index 2a17a4afbe..dc9d2a985c 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift @@ -310,8 +310,8 @@ final class TextProcessingContentComponent: Component { if style.isCreator { environment.controller()?.push(textAlertController( context: component.context, - title: "Delete Style", - text: "Are you sure you want to delete this style? It will be removed for everyone who installed it.", + title: environment.strings.TextProcessing_AlertCreatorDeleteStyle_Title, + text: environment.strings.TextProcessing_AlertCreatorDeleteStyle_Text, actions: [ TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: environment.strings.Common_Delete, action: { [weak self] in @@ -326,8 +326,8 @@ final class TextProcessingContentComponent: Component { } else { environment.controller()?.push(textAlertController( context: component.context, - title: "Delete Style", - text: "Are you sure you want to delete this style?", + title: environment.strings.TextProcessing_AlertDeleteStyle_Title, + text: environment.strings.TextProcessing_AlertDeleteStyle_Text, actions: [ TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: environment.strings.Common_Delete, action: { [weak self] in @@ -354,7 +354,7 @@ final class TextProcessingContentComponent: Component { var items: [ContextMenuItem] = [] if style.isAuthor { items.append(.action(ContextMenuActionItem( - text: "Edit Style", + text: environment.strings.TextProcessing_StyleMenu_Edit, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, @@ -369,7 +369,7 @@ final class TextProcessingContentComponent: Component { )) } items.append(.action(ContextMenuActionItem( - text: "Share Style", //TODO:localize + text: environment.strings.TextProcessing_StyleMenu_Share, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, @@ -383,7 +383,7 @@ final class TextProcessingContentComponent: Component { }) )) items.append(.action(ContextMenuActionItem( - text: "Delete Style", + text: environment.strings.TextProcessing_StyleMenu_Delete, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) @@ -576,7 +576,7 @@ final class TextProcessingContentComponent: Component { let descriptionSize = previewDescription.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "Add this style to instantly\nrewrite your messages.", font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor)), + text: .plain(NSAttributedString(string: environment.strings.TextProcessing_StylePreview_Subtitle, font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor)), horizontalAlignment: .center, maximumNumberOfLines: 0, lineSpacing: 0.12 @@ -805,7 +805,6 @@ final class TextProcessingContentComponent: Component { if component.styles.filter({ $0.isAuthor }).count >= maxStyles { if !hasPremium { - //TODO:localize let context = component.context var replaceImpl: ((ViewController) -> Void)? let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .aiTools, forceDark: false, action: { @@ -824,8 +823,8 @@ final class TextProcessingContentComponent: Component { } else { environment.controller()?.push(textAlertController( context: component.context, - title: "Too Many Styles", - text: "Please delete some of your saved styles to create a new one.", + title: environment.strings.TextProcessing_AlertTooManyStyles_Title, + text: environment.strings.TextProcessing_AlertTooManyStyles_Text, actions: [ TextAlertAction(type: .defaultAction, title: environment.strings.Common_OK, action: {}), ] @@ -1435,8 +1434,7 @@ private final class TextProcessingSheetComponent: Component { dismiss(true) } case let .preview(style, _, _, isAlreadyAdded, added): - //TODO:localize - actionButtonTitle = isAlreadyAdded ? "Done" : "Add Style" + actionButtonTitle = isAlreadyAdded ? environmentValue.strings.TextProcessing_StyleMenu_ButtonClose : environmentValue.strings.TextProcessing_StyleMenu_ButtonAdd isMainActionEnabled = !self.isPerformingMainAction performMainAction = { [weak self] in guard let self, let component = self.component else { @@ -1797,10 +1795,10 @@ private final class TextProcessingSheetComponent: Component { )), content: AnyComponent(VStack([ AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "\(styleCreatedToastData.style.title) style created!", font: Font.semibold(14.0), textColor: .white)), + text: .plain(NSAttributedString(string: environmentValue.strings.TextProcessing_ToastStyleCreated_Title(styleCreatedToastData.style.title).string, font: Font.semibold(14.0), textColor: .white)), ))), AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent( - text: .markdown(text: "Press and hold a style to edit or share the link.", attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil })), + text: .markdown(text: environmentValue.strings.TextProcessing_ToastStyleCreated_Text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil })), maximumNumberOfLines: 0 ))) ], alignment: .left, spacing: 6.0)), diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingStyleSelectionComponent.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingStyleSelectionComponent.swift index d7529e9a3b..e265719ceb 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingStyleSelectionComponent.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingStyleSelectionComponent.swift @@ -638,11 +638,10 @@ private final class CreateItemComponent: Component { containerSize: CGSize(width: 100.0, height: 100.0) ) - //TODO:localize let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "Add Style", font: Font.medium(10.0), textColor: iconTintColor)) + text: .plain(NSAttributedString(string: component.strings.TextProcessing_StyleList_Add, font: Font.medium(10.0), textColor: iconTintColor)) )), environment: {}, containerSize: CGSize(width: availableSize.width, height: 100.0) diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingTranslateContentComponent.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingTranslateContentComponent.swift index 7f5bc81b5d..186e69904f 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingTranslateContentComponent.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingTranslateContentComponent.swift @@ -382,9 +382,8 @@ final class TextProcessingTranslateContentComponent: Component { } toTitle = "" case .preview: - //TODO:localize - fromFormat = "Before" - toFormat = "After" + fromFormat = component.strings.TextProcessing_StylePreview_Before + toFormat = component.strings.TextProcessing_StylePreview_After toTitle = "" } @@ -407,14 +406,12 @@ final class TextProcessingTranslateContentComponent: Component { } if case .stylize = component.mode { - //TODO:localize if case .style = component.externalState.style, let style = component.styles.first(where: { $0.id == component.externalState.style }), let authorPeer = style.authorPeer { let footerText: String - //TODO:localize if let addressName = authorPeer.addressName { - footerText = "Style by [@\(addressName)](\(authorPeer.id.toInt64()))" + footerText = component.strings.TextProcessing_StyleFooterAuthor("@" + addressName).string } else { - footerText = "Style by [\(authorPeer.displayTitle(strings: component.strings, displayOrder: .firstLast))](\(authorPeer.id.toInt64()))" + footerText = component.strings.TextProcessing_StyleFooterAuthor(authorPeer.displayTitle(strings: component.strings, displayOrder: .firstLast)).string } component.externalState.sectionFooter = AnyComponentWithIdentity(id: "style_by_\(authorPeer.id.toInt64())", component: AnyComponent(MultilineTextComponent( text: .markdown(text: footerText, attributes: MarkdownAttributes( @@ -448,7 +445,7 @@ final class TextProcessingTranslateContentComponent: Component { } else if case let .preview(_, _, authorPeer, userCount, _) = component.mode { component.externalState.sectionHeader = AnyComponentWithIdentity(id: "preview", component: AnyComponent(HStack([ AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( - text: .markdown(text: "EXAMPLE", attributes: MarkdownAttributes( + text: .markdown(text: component.strings.TextProcessing_StylePreview_ExampleHeader, attributes: MarkdownAttributes( body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: component.theme.list.freeTextColor), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: component.theme.list.freeTextColor), link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: component.theme.list.itemAccentColor), @@ -464,7 +461,7 @@ final class TextProcessingTranslateContentComponent: Component { tintColor: component.theme.list.itemAccentColor ))), AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "ANOTHER EXAMPLE", font: Font.regular(13.0), textColor: component.theme.list.itemAccentColor)) + text: .plain(NSAttributedString(string: component.strings.TextProcessing_StylePreview_ExampleHeaderRefresh, font: Font.regular(13.0), textColor: component.theme.list.itemAccentColor)) ))) ], spacing: 2.0)), action: { [weak self] in @@ -478,18 +475,17 @@ final class TextProcessingTranslateContentComponent: Component { ))), ], spacing: 8.0, alignment: .alternatingLeftRight))) - let userCountString = userCount == 1 ? "Used by 1 person" : "Used by \(userCount) people" + let userCountString = component.strings.TextProcessing_StyleFooterUserCount(Int32(userCount)) let footerText: String - //TODO:localize if let authorPeer { if let addressName = authorPeer.addressName { - footerText = "\(userCountString). Created by [@\(addressName)](\(authorPeer.id.toInt64()))" + footerText = component.strings.TextProcessing_StyleFooterCreatedByFormat(userCountString, component.strings.TextProcessing_StyleFooterCreatedBy("@" + addressName).string).string } else { - footerText = "\(userCountString). Created by [\(authorPeer.displayTitle(strings: component.strings, displayOrder: .firstLast))](\(authorPeer.id.toInt64()))" + footerText = component.strings.TextProcessing_StyleFooterCreatedByFormat(userCountString, component.strings.TextProcessing_StyleFooterCreatedBy(authorPeer.displayTitle(strings: component.strings, displayOrder: .firstLast)).string).string } } else { - footerText = "\(userCountString)." + footerText = component.strings.TextProcessing_StyleFooterCreatedBySimpleFormat(userCountString).string } component.externalState.sectionFooter = AnyComponentWithIdentity(id: "style_by_\(authorPeer?.id.toInt64() ?? 0)", component: AnyComponent(MultilineTextComponent( text: .markdown(text: footerText, attributes: MarkdownAttributes( diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextStyleEditScreen.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextStyleEditScreen.swift index 88e0e2fa9d..ae218bcc95 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextStyleEditScreen.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextStyleEditScreen.swift @@ -335,7 +335,6 @@ final class TextStyleEditContentComponent: Component { contentHeight += iconBackgroundSize.height + iconSpacing - //TODO:localize var titleSectionItems: [AnyComponentWithIdentity] = [] titleSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListMultilineTextFieldItemComponent( externalState: component.externalState.titleInputState, @@ -347,7 +346,7 @@ final class TextStyleEditContentComponent: Component { resetText: resetTitle.flatMap { resetTitle in return ListMultilineTextFieldItemComponent.ResetText(value: resetTitle) }, - placeholder: "Style Name (for example, \"Pirate\")", + placeholder: environment.strings.TextProcessing_EditStyle_NamePlaceholder, autocapitalizationType: .sentences, autocorrectionType: .default, characterLimit: 12, @@ -379,7 +378,6 @@ final class TextStyleEditContentComponent: Component { contentHeight += titleSectionSize.height contentHeight += sectionSpacing - //TODO:localize var textSectionItems: [AnyComponentWithIdentity] = [] textSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListMultilineTextFieldItemComponent( externalState: component.externalState.textInputState, @@ -391,7 +389,7 @@ final class TextStyleEditContentComponent: Component { resetText: resetText.flatMap { resetText in return ListMultilineTextFieldItemComponent.ResetText(value: resetText) }, - placeholder: "Instructions (for example, \"Write like a swashbuckling pirate. Use arr, ye, matey, and talk about treasure, the sea, and rum\")", + placeholder: environment.strings.TextProcessing_EditStyle_TextPlaceholder, placeholderDefinesMinHeight: true, autocapitalizationType: .sentences, autocorrectionType: .default, @@ -439,7 +437,7 @@ final class TextStyleEditContentComponent: Component { title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "Delete Style", + string: environment.strings.TextProcessing_EditStyle_Delete, font: Font.regular(17.0), textColor: environment.theme.list.itemDestructiveColor )), @@ -453,8 +451,8 @@ final class TextStyleEditContentComponent: Component { } environment.controller()?.push(textAlertController( context: component.context, - title: "Delete Style", - text: "Are you sure you want to delete this style? It will be removed for everyone who installed it.", + title: environment.strings.TextProcessing_AlertCreatorDeleteStyle_Title, + text: environment.strings.TextProcessing_AlertCreatorDeleteStyle_Text, actions: [ TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: environment.strings.Common_Delete, action: { [weak self] in @@ -503,7 +501,7 @@ final class TextStyleEditContentComponent: Component { selected: component.externalState.isLinkToProfileEnabled ))), AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "Add a link to my account", font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor)) + text: .plain(NSAttributedString(string: environment.strings.TextProcessing_EditStyle_AddLink, font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor)) ))) ], spacing: 10.0)), effectAlignment: .center, @@ -779,11 +777,11 @@ private final class TextStyleEditSheetComponent: Component { let titleString: String switch component.mode { case .create: - titleString = "New Style" - actionButtonTitle = "Create" + titleString = environmentValue.strings.TextProcessing_EditStyle_TitleCreate + actionButtonTitle = environmentValue.strings.TextProcessing_EditStyle_ActionCreate case .edit: - titleString = "Edit Style" - actionButtonTitle = "Save" + titleString = environmentValue.strings.TextProcessing_EditStyle_TitleEdit + actionButtonTitle = environmentValue.strings.TextProcessing_EditStyle_ActionEdit } let sheetSize = self.sheet.update( diff --git a/submodules/TelegramUI/Sources/ChatControllerContentData.swift b/submodules/TelegramUI/Sources/ChatControllerContentData.swift index fc9d8e5b4d..fcc7e6e66f 100644 --- a/submodules/TelegramUI/Sources/ChatControllerContentData.swift +++ b/submodules/TelegramUI/Sources/ChatControllerContentData.swift @@ -609,8 +609,7 @@ extension ChatControllerImpl { } else { var customSubtitle: String? if savedMessagesChatsTip { - //TODO:localize - customSubtitle = "Tap to view as chats" + customSubtitle = strings.Chat_SavedMessagesStatusViewAsChats } strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, customSubtitle: customSubtitle, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, hidePeerStatus: false, isEnabled: hasPeerInfo) diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index addf21cee3..6d916852c5 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -330,8 +330,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { } |> take(1) |> timeout(1.0, queue: .mainQueue(), alternate: .single(false)) |> deliverOnMainQueue).startStandalone(next: { value in if value { let presentationData = params.context.sharedContext.currentPresentationData.with { $0 } - //TODO:localize - let toastController = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "Device is muted.", timeout: 4.0, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in + let toastController = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Chat_ToastVoiceMessageDeviceMuted, timeout: 4.0, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return true }) params.present(toastController, nil, .current) diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index d77a7eb022..d448a26b79 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -2054,8 +2054,7 @@ func openResolvedUrlImpl( guard let emojiFileId = style.emojiFileId, let file = await context.engine.stickers.resolveInlineStickers(fileIds: [emojiFileId]).get().first?.value else { return } - //TODO:localize - present(UndoOverlayController(presentationData: presentationData, content: .customEmoji(context: context, file: file, loop: false, title: "Style Added", text: "Tap 'AI' → '\(style.title)' when typing your next long message.", undoText: nil, customAction: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in + present(UndoOverlayController(presentationData: presentationData, content: .customEmoji(context: context, file: file, loop: false, title: presentationData.strings.TextProcessing_ToastStyleAdded_Title, text: presentationData.strings.TextProcessing_ToastStyleAdded_Text(style.title).string, undoText: nil, customAction: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return true }), nil) }