diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index f93a81115f..0f732dfa3e 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -16096,3 +16096,53 @@ Error: %8$@"; "MediaPicker.LivePhoto.LiveOff" = "Live Off"; "AuthConfirmation.UnverifiedApp" = "Unverified App"; + +"Chat.ManagedBot.CreatedTitle" = "**%@** is ready!"; +"Chat.ManagedBot.CreatedText" = "Tap **Start** below to test your new chatbot. Its behavior is defined by **%@**."; +"Chat.CreateBotLink" = "Create Bot"; + +"Chat.InlineTopicMenu.TabAll" = "All"; + +"CreateBot.Title" = "Create Bot"; +"CreateBot.Text" = "[%@]() wound like to create and manage a chatbot on your behalf."; +"CreateBot.SectionName" = "BOT NAME"; +"CreateBot.SectionUsername" = "BOT USERNAME"; +"CreateBot.NamePlaceholder" = "Name"; +"CreateBot.UsernameStatus.Checking" = "Checking..."; +"CreateBot.UsernameStatus.Invalid" = "You can only use **a-z**, **0-9** and underscores."; +"CreateBot.UsernameStatus.Taken" = "This username is already taken."; +"CreateBot.UsernameStatus.Short" = "A username must have at least 5 characters."; +"CreateBot.UsernameStatus.Number" = "A username can't start with a number"; +"CreateBot.UsernameStatus.Link" = "Link: t.me/%@"; +"CreateBot.UnsavedAlert.Title" = "Unsaved Changes"; +"CreateBot.UnsavedAlert.Text" = "You have not finished creating a bot."; +"CreateBot.UnsavedAlert.Discard" = "Discard"; +"CreateBot.ActionButton" = "Create"; +"CreateBot.InputBadge" = "edit"; + +"PeerInfo.ManagedBotFooter" = "Created and managed by [@%@]()"; + +"Conversation.ReadAllPollVotes" = "Read All Poll Votes"; +"Chat.PanelSetBotPhoto" = "Set Profile Photo"; + +"TextProcessing.TitleEdit" = "AI Editor"; +"TextProcessing.TitleTranslate" = "Translation"; +"TextProcessing.TabTranslate" = "Translate"; +"TextProcessing.TabStylize" = "Style"; +"TextProcessing.TabFix" = "Fix"; +"TextProcessing.ActionTitleNonPremium" = "Increase Limit"; +"TextProcessing.ActionValueNonPremium" = "X50"; +"TextProcessing.ActionApply" = "Apply"; +"TextProcessing.ActionClose" = "Close"; +"TextProcessing.LimitToast.Title" = "Daily limit reached"; +"TextProcessing.LimitToast.Text" = "Get **Telegram Premium** for **50x** more text edits per day."; +"TextProcessing.Emojify" = "Emojify"; +"TextProcessing.TextExpand" = "more"; +"TextProcessing.TextCollapse" = "less"; +"TextProcessing.Translate.FromLanguage" = "From {}"; +"TextProcessing.Translate.ToLanguage" = "To {}"; +"TextProcessing.OriginalBadge" = "Original:"; +"TextProcessing.OriginalStyleBadge" = "Original"; +"TextProcessing.ResultBadge" = "Result"; +"TextProcessing.Translate.LanguageStyle" = "%1$@ (%2$@)"; +"TextProcessing.StyleTooltip" = "Select Style"; diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index ca9f40784e..c2be5dbc57 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -19,6 +19,7 @@ import ChatSendMessageActionUI import MinimizedContainer import ComponentFlow import GlassBackgroundComponent +import TextFormat public enum AttachmentButtonType: Equatable { case gallery @@ -775,19 +776,17 @@ public class AttachmentController: ViewController, MinimizableController { mode: .edit( saveRestoreStateId: nil, completion: { [weak self] text in - //TODO:localize guard let self, let mediaPickerContext = self.mediaPickerContext else { return } - self.panel.updateCaption(NSAttributedString(string: text.text)) - mediaPickerContext.setCaption(NSAttributedString(string: text.text)) + self.panel.updateCaption(chatInputStateStringWithAppliedEntities(text.text, entities: text.entities)) + mediaPickerContext.setCaption(chatInputStateStringWithAppliedEntities(text.text, entities: text.entities)) }, send: { [weak self] text in - //TODO:localize guard let self, let mediaPickerContext = self.mediaPickerContext else { return } - mediaPickerContext.setCaption(NSAttributedString(string: text.text)) + mediaPickerContext.setCaption(chatInputStateStringWithAppliedEntities(text.text, entities: text.entities)) mediaPickerContext.send(mode: .generic, attachmentMode: .media, parameters: nil) }, sendContextActions: nil diff --git a/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift b/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift index 23bd372724..39aec40872 100644 --- a/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift @@ -251,8 +251,7 @@ public final class ChatBotInfoItemNode: ListViewItemNode { let textString: NSAttributedString let textSpacing: CGFloat if let peer = item.peer, let managedByBot = item.managedByBot { - //TODO:localize - titleString = parseMarkdownIntoAttributedString("**\(peer.compactDisplayTitle)** is ready!", attributes: MarkdownAttributes( + titleString = parseMarkdownIntoAttributedString(item.presentationData.strings.Chat_ManagedBot_CreatedTitle(peer.compactDisplayTitle).string, attributes: MarkdownAttributes( body: MarkdownAttributeSet(font: messageFont, textColor: item.presentationData.theme.theme.chat.message.infoPrimaryTextColor), bold: MarkdownAttributeSet(font: messageBoldFont, textColor: item.presentationData.theme.theme.chat.message.infoPrimaryTextColor), link: MarkdownAttributeSet(font: messageFont, textColor: item.presentationData.theme.theme.chat.message.infoPrimaryTextColor), @@ -260,7 +259,7 @@ public final class ChatBotInfoItemNode: ListViewItemNode { return ("URL", url) } )) - let rawTextString = "Tap **Start** below to test your new chatbot. Its behavior is defined by **\(managedByBot.compactDisplayTitle)**." + let rawTextString = item.presentationData.strings.Chat_ManagedBot_CreatedText(managedByBot.compactDisplayTitle).string textString = parseMarkdownIntoAttributedString(rawTextString, attributes: MarkdownAttributes( body: MarkdownAttributeSet(font: messageFont, textColor: item.presentationData.theme.theme.chat.message.infoPrimaryTextColor), bold: MarkdownAttributeSet(font: messageBoldFont, textColor: item.presentationData.theme.theme.chat.message.infoPrimaryTextColor), diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index 312f420885..636399f4ef 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -516,8 +516,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent case "telegram_channel_direct": actionTitle = item.presentationData.strings.Chat_ContactChannel case "telegram_newbot": - //TODO:localize - actionTitle = "Create Bot" + actionTitle = item.presentationData.strings.Chat_CreateBotLink default: break } diff --git a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift index f5d15db9e7..19fa39ba65 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift @@ -1263,8 +1263,7 @@ public final class ChatSideTopicsPanel: Component { if forumManagedByUser { titleText = component.strings.Chat_InlineTopicMenu_NewForumThreadTab } else { - //TODO:localize - titleText = "All" + titleText = component.strings.Chat_InlineTopicMenu_TabAll } } else { titleText = component.strings.Chat_InlineTopicMenu_AllTab @@ -1405,8 +1404,7 @@ public final class ChatSideTopicsPanel: Component { if forumManagedByUser { titleText = component.strings.Chat_InlineTopicMenu_NewForumThreadTab } else { - //TODO:localize - titleText = "All" + titleText = component.strings.Chat_InlineTopicMenu_TabAll } } else { titleText = component.strings.Chat_InlineTopicMenu_AllTab diff --git a/submodules/TelegramUI/Components/CreateBotScreen/Sources/CreateBotScreen.swift b/submodules/TelegramUI/Components/CreateBotScreen/Sources/CreateBotScreen.swift index fda1953b09..4306c1426b 100644 --- a/submodules/TelegramUI/Components/CreateBotScreen/Sources/CreateBotScreen.swift +++ b/submodules/TelegramUI/Components/CreateBotScreen/Sources/CreateBotScreen.swift @@ -200,7 +200,7 @@ final class CreateBotContentComponent: Component { let titleSize = self.title.update( transition: .immediate, component: AnyComponent(BalancedTextComponent( - text: .plain(NSAttributedString(string: "Create Bot", font: Font.bold(24.0), textColor: environment.theme.list.itemPrimaryTextColor)), + text: .plain(NSAttributedString(string: environment.strings.CreateBot_Title, font: Font.bold(24.0), textColor: environment.theme.list.itemPrimaryTextColor)), horizontalAlignment: .center, maximumNumberOfLines: 0, lineSpacing: 0.12 @@ -230,7 +230,7 @@ final class CreateBotContentComponent: Component { let subtitleSize = self.subtitle.update( transition: .immediate, component: AnyComponent(BalancedTextComponent( - text: .markdown(text: "[\(component.parentPeer.debugDisplayTitle)]() wound like to create and manage a chatbot on your behalf.", attributes: markdownAttributes), + text: .markdown(text: environment.strings.CreateBot_Text(component.parentPeer.debugDisplayTitle).string, attributes: markdownAttributes), horizontalAlignment: .center, maximumNumberOfLines: 0, lineSpacing: 0.12 @@ -256,7 +256,7 @@ final class CreateBotContentComponent: Component { style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "BOT NAME", + string: environment.strings.CreateBot_SectionName, font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor )), @@ -272,7 +272,7 @@ final class CreateBotContentComponent: Component { strings: environment.strings, initialText: "", resetText: isFirstTime ? ListMultilineTextFieldItemComponent.ResetText(value: component.initialTitle ?? "") : nil, - placeholder: "Name", + placeholder: environment.strings.CreateBot_NamePlaceholder, autocapitalizationType: .words, autocorrectionType: .no, characterLimit: 64, @@ -327,23 +327,23 @@ final class CreateBotContentComponent: Component { switch self.usernameCheckingStatus?.status ?? .valid { case .checking: usernameFooterString = NSAttributedString( - string: "Checking...", + string: environment.strings.CreateBot_UsernameStatus_Checking, font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor ) case .invalid: - let errorText = "You can only use **a-z**, **0-9** and underscores." + let errorText = environment.strings.CreateBot_UsernameStatus_Invalid usernameFooterString = parseMarkdownIntoAttributedString(errorText, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemDestructiveColor), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.itemDestructiveColor), link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemDestructiveColor), linkAttribute: { contents in return ("URL", contents) })) case .taken: - let errorText = "This username is already taken." + let errorText = environment.strings.CreateBot_UsernameStatus_Taken usernameFooterString = parseMarkdownIntoAttributedString(errorText, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemDestructiveColor), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.itemDestructiveColor), link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemDestructiveColor), linkAttribute: { contents in return ("URL", contents) })) case .valid: usernameFooterString = NSAttributedString( - string: "Link: t.me/\(value)", + string: environment.strings.CreateBot_UsernameStatus_Link(value).string, font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor ) @@ -352,11 +352,11 @@ final class CreateBotContentComponent: Component { let errorText: String switch error { case .insufficientLength: - errorText = "A username must have at least 5 characters." + errorText = environment.strings.CreateBot_UsernameStatus_Short case .startsWithNumber: - errorText = "A username can't start with a number" + errorText = environment.strings.CreateBot_UsernameStatus_Number case .unsupportedCharacters: - errorText = "You can only use **a-z**, **0-9** and underscores." + errorText = environment.strings.CreateBot_UsernameStatus_Invalid } usernameFooterString = parseMarkdownIntoAttributedString(errorText, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemDestructiveColor), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.itemDestructiveColor), link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemDestructiveColor), linkAttribute: { contents in return ("URL", contents) @@ -370,7 +370,7 @@ final class CreateBotContentComponent: Component { style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "BOT USERNAME", + string: environment.strings.CreateBot_SectionUsername, font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor )), @@ -513,17 +513,16 @@ private final class CreateBotSheetComponent: Component { return false } - //TODO:localize let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let _ = presentationData let alertController = textAlertController( context: component.context, - title: "Unsaved Changes", - text: "You have not finished creating a bot.", + title: presentationData.strings.CreateBot_UnsavedAlert_Title, + text: presentationData.strings.CreateBot_UnsavedAlert_Text, actions: [ - TextAlertAction(type: .genericAction, title: "Cancel", action: { + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { }), - TextAlertAction(type: .destructiveAction, title: "Discard", action: { [weak self] in + TextAlertAction(type: .destructiveAction, title: presentationData.strings.CreateBot_UnsavedAlert_Discard, action: { [weak self] in guard let self, let component = self.component else { return } @@ -650,10 +649,9 @@ private final class CreateBotSheetComponent: Component { case .generic: text = environment.strings.Login_UnknownError case .occupied: - text = "This username already exists." + text = environment.strings.CreateBot_UsernameStatus_Taken } - //TODO:localize let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) self.environment?.controller()?.push(textAlertController( context: component.context, @@ -959,7 +957,7 @@ private final class ActionButtonsComponent: Component { content: AnyComponentWithIdentity( id: AnyHashable(0), component: AnyComponent(ButtonTextContentComponent( - text: "Create", //TODO:localize + text: component.strings.CreateBot_ActionButton, badge: 0, textColor: component.theme.list.itemCheckColors.foregroundColor, badgeBackground: component.theme.list.itemCheckColors.foregroundColor, @@ -1068,11 +1066,10 @@ private final class EditLabelComponent: Component { let verticalInset: CGFloat = 4.0 let rightInset: CGFloat = 16.0 - //TODO:localize let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "edit", font: Font.regular(11.0), textColor: component.theme.list.itemAccentColor)) + text: .plain(NSAttributedString(string: component.strings.CreateBot_InputBadge, font: Font.regular(11.0), textColor: component.theme.list.itemAccentColor)) )), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoProfileItems.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoProfileItems.swift index ba1dec9ae1..5efc444d18 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoProfileItems.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoProfileItems.swift @@ -513,8 +513,7 @@ func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationD })) if let managedByBot = data.managedByBot { - //TODO:localize - items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: ItemBotAddToChatInfo, icon: .managedBot, text: "Created and managed by [@\(managedByBot.compactDisplayTitle)]()", linkAction: { _ in + items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: ItemBotAddToChatInfo, icon: .managedBot, text: presentationData.strings.PeerInfo_ManagedBotFooter(managedByBot.compactDisplayTitle).string, linkAction: { _ in interaction.openPeerInfo(managedByBot._asPeer(), false) })) } else { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift index cf9db2dc9c..7d77f7bac8 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift @@ -689,7 +689,6 @@ extension PeerInfoScreenNode { hasPaidFee = true } if !hasPaidFee { - //TODO:localize items.append(.action(ContextMenuActionItem(text: "Return Fee", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Media Grid/Paid"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingLanguageSelectionComponent.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingLanguageSelectionComponent.swift index 96b43edbb8..fb3d20a3cd 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingLanguageSelectionComponent.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingLanguageSelectionComponent.swift @@ -410,7 +410,7 @@ final class TextProcessingLanguageSelectionComponent: Component { if component.ignoredTranslationLanguages.contains(item) { return nil } - return Language(id: item, languageCode: item, name: localizedLanguageName(strings: component.strings, language: item)) + return Language(id: item, languageCode: item, name: localizedLanguageName(strings: component.strings, language: item, kind: .neutral)) } var topIds: [String] = [] if !topIds.contains(component.selectedLanguageCode), let item = self.mainItems.first(where: { $0.languageCode == component.selectedLanguageCode }) { diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift index 572d71e4ea..db3c9e4245 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift @@ -229,7 +229,7 @@ final class TextProcessingContentComponent: Component { tabs.append(TabBarComponent.Item( content: .customItem(TabBarComponent.Item.Content.CustomItem( id: "translate", - title: "Translate", + title: environment.strings.TextProcessing_TabTranslate, icon: .bundleIcon(name: "TextProcessing/TabTranslate") )), action: { [weak self] _ in @@ -251,7 +251,7 @@ final class TextProcessingContentComponent: Component { tabs.append(TabBarComponent.Item( content: .customItem(TabBarComponent.Item.Content.CustomItem( id: "stylize", - title: "Style", + title: environment.strings.TextProcessing_TabStylize, icon: .bundleIcon(name: "TextProcessing/TabStylize") )), action: { [weak self] _ in @@ -274,7 +274,7 @@ final class TextProcessingContentComponent: Component { tabs.append(TabBarComponent.Item( content: .customItem(TabBarComponent.Item.Content.CustomItem( id: "fix", - title: "Fix", + title: environment.strings.TextProcessing_TabFix, icon: .bundleIcon(name: "TextProcessing/TabFix") )), action: { [weak self] _ in @@ -437,31 +437,6 @@ final class TextProcessingContentComponent: Component { var actionsSectionItems: [AnyComponentWithIdentity] = [] if case .translate = component.mode { - /*if let replaceText = component.replaceText { - //TODO:localize - actionsSectionItems.append(AnyComponentWithIdentity(id: "replace", component: AnyComponent(ListActionItemComponent( - theme: theme, - style: .glass, - title: AnyComponent( - MultilineTextComponent( - text: .plain(NSAttributedString( - string: "Replace with Translation", - font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), - textColor: theme.list.itemAccentColor - )), - maximumNumberOfLines: 1 - ) - ), - leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Replace", tintColor: theme.list.itemAccentColor))), false), - action: { [weak state] _ in - guard let state else { - return - } - replaceText(state.translatedText?.0 ?? state.text, state.translatedText?.1 ?? state.entities) - component.dismiss() - } - )))) - }*/ if let copyTranslation = component.copyCurrentResult { actionsSectionItems.append(AnyComponentWithIdentity(id: "copy", component: AnyComponent(ListActionItemComponent( theme: environment.theme, @@ -489,7 +464,7 @@ final class TextProcessingContentComponent: Component { title: AnyComponent( MultilineTextComponent( text: .plain(NSAttributedString( - string: "Translate Entire Chat", + string: environment.strings.Localization_TranslateEntireChat, font: Font.regular(17.0), textColor: environment.theme.list.itemAccentColor )), @@ -765,13 +740,12 @@ private final class TextProcessingSheetComponent: Component { var performSendAction: ((TextWithEntities) -> Void)? var hasLongPressActions = false let isMainActionEnabled: Bool - //TODO:localize (for AI: don't remove this line) let actionButtonTitle: String var actionButtonShowsIncreaseLimit = false if self.contentExternalState.nonPremiumFloodTriggered { isMainActionEnabled = true - actionButtonTitle = "Increase Limit" + actionButtonTitle = environmentValue.strings.TextProcessing_ActionTitleNonPremium actionButtonShowsIncreaseLimit = true performMainAction = { [weak self] in guard let self, let component = self.component else { @@ -780,8 +754,8 @@ private final class TextProcessingSheetComponent: Component { let context = component.context var replaceImpl: ((ViewController) -> Void)? - let controller = component.context.sharedContext.makePremiumDemoController(context: component.context, subject: .doubleLimits, forceDark: false, action: { - let controller = component.context.sharedContext.makePremiumIntroController(context: context, source: .settings, forceDark: false, dismissed: nil) + let controller = component.context.sharedContext.makePremiumDemoController(context: component.context, subject: .aiTools, forceDark: false, action: { + let controller = component.context.sharedContext.makePremiumIntroController(context: context, source: .aiTools, forceDark: false, dismissed: nil) replaceImpl?(controller) }, dismissed: nil) replaceImpl = { [weak controller] c in @@ -792,7 +766,7 @@ private final class TextProcessingSheetComponent: Component { } else { switch component.mode { case let .edit(_, completion, send, sendContextActions): - actionButtonTitle = "Apply" + actionButtonTitle = environmentValue.strings.TextProcessing_ActionApply performSendAction = send isMainActionEnabled = !self.contentExternalState.isProcessing hasLongPressActions = sendContextActions != nil @@ -806,7 +780,7 @@ private final class TextProcessingSheetComponent: Component { dismiss(true) } case .translate: - actionButtonTitle = "Close" + actionButtonTitle = environmentValue.strings.TextProcessing_ActionClose isMainActionEnabled = true performMainAction = { dismiss(true) @@ -827,9 +801,9 @@ private final class TextProcessingSheetComponent: Component { let titleString: String switch component.mode { case .edit: - titleString = "AI Editor" + titleString = environmentValue.strings.TextProcessing_TitleEdit case .translate: - titleString = "Translation" + titleString = environmentValue.strings.TextProcessing_TitleTranslate } let sheetSize = self.sheet.update( @@ -903,6 +877,7 @@ private final class TextProcessingSheetComponent: Component { bottomItem: AnyComponent( ActionButtonsComponent( theme: theme, + strings: environmentValue.strings, actionTitle: actionButtonTitle, actionButtonShowsIncreaseLimit: actionButtonShowsIncreaseLimit, action: isMainActionEnabled ? performMainAction : nil, @@ -982,10 +957,10 @@ private final class TextProcessingSheetComponent: Component { )), content: AnyComponent(VStack([ AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "Daily limit reached", font: Font.semibold(14.0), textColor: .white)), + text: .plain(NSAttributedString(string: environmentValue.strings.TextProcessing_LimitToast_Title, font: Font.semibold(14.0), textColor: .white)), ))), AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent( - text: .markdown(text: "Get **Telegram Premium** for **50x** more text edits per day.", attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil })), + text: .markdown(text: environmentValue.strings.TextProcessing_LimitToast_Text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil })), maximumNumberOfLines: 0 ))) ], alignment: .left, spacing: 6.0)), @@ -1302,6 +1277,7 @@ private final class TitleComponent: Component { private final class ActionButtonsComponent: Component { let theme: PresentationTheme + let strings: PresentationStrings let actionTitle: String let actionButtonShowsIncreaseLimit: Bool let action: (() -> Void)? @@ -1310,6 +1286,7 @@ private final class ActionButtonsComponent: Component { init( theme: PresentationTheme, + strings: PresentationStrings, actionTitle: String, actionButtonShowsIncreaseLimit: Bool, action: (() -> Void)?, @@ -1317,6 +1294,7 @@ private final class ActionButtonsComponent: Component { longPressSendAction: ((UIView) -> Void)? ) { self.theme = theme + self.strings = strings self.actionTitle = actionTitle self.actionButtonShowsIncreaseLimit = actionButtonShowsIncreaseLimit self.action = action @@ -1328,6 +1306,9 @@ private final class ActionButtonsComponent: Component { if lhs.theme !== rhs.theme { return false } + if lhs.strings !== rhs.strings { + return false + } if lhs.actionTitle != rhs.actionTitle { return false } @@ -1380,6 +1361,7 @@ private final class ActionButtonsComponent: Component { )))) if component.actionButtonShowsIncreaseLimit { actionButtonContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(IncreaseLimitBadgeComponent( + title: component.strings.TextProcessing_ActionValueNonPremium, fillColor: component.theme.list.itemCheckColors.foregroundColor, foregroundColor: .clear )))) @@ -1484,18 +1466,24 @@ private final class ActionButtonsComponent: Component { } private final class IncreaseLimitBadgeComponent: Component { + let title: String let fillColor: UIColor let foregroundColor: UIColor init( + title: String, fillColor: UIColor, foregroundColor: UIColor ) { + self.title = title self.fillColor = fillColor self.foregroundColor = foregroundColor } static func ==(lhs: IncreaseLimitBadgeComponent, rhs: IncreaseLimitBadgeComponent) -> Bool { + if lhs.title != rhs.title { + return false + } if lhs.fillColor != rhs.fillColor { return false } @@ -1532,7 +1520,7 @@ private final class IncreaseLimitBadgeComponent: Component { let topInset: CGFloat = 1.0 let bottomInset: CGFloat = 0.0 - let text = NSAttributedString(string: "X50", font: Font.with(size: 14.0, design: .round, weight: .semibold), textColor: .clear) + let text = NSAttributedString(string: component.title, font: Font.with(size: 14.0, design: .round, weight: .semibold), textColor: .clear) let rawTextSize = text.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil) let textSize = CGSize(width: ceil(rawTextSize.width), height: ceil(rawTextSize.height)) let backgroundSize = CGSize(width: leftInset + rightInset + textSize.width, height: topInset + bottomInset + textSize.height) diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingStyleSelectionComponent.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingStyleSelectionComponent.swift index cb7fa7c87c..566eb50b2f 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingStyleSelectionComponent.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingStyleSelectionComponent.swift @@ -13,7 +13,7 @@ func localizedStyleName(strings: PresentationStrings, styleId: TelegramComposeAI case .neutral: return strings.TextProcessingStyle_Neutral case let .style(name): - if let value = strings.primaryComponent.dict["TextProcessingStyle_\(name)"] { + if let value = strings.primaryComponent.dict["TextProcessingStyle.\(name)"] { return value } else { return name.capitalized diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingTextAreaComponent.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingTextAreaComponent.swift index fb22faf9df..d2d7652a88 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingTextAreaComponent.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingTextAreaComponent.swift @@ -21,7 +21,7 @@ final class TextProcessingTextAreaComponent: Component { let context: AccountContext let theme: PresentationTheme let strings: PresentationStrings - let titlePrefix: String + let titleFormat: String let title: String let titleAction: ((UIView) -> Void)? let isExpanded: (value: Bool, toggle: () -> Void)? @@ -37,7 +37,7 @@ final class TextProcessingTextAreaComponent: Component { context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, - titlePrefix: String, + titleFormat: String, title: String, titleAction: ((UIView) -> Void)?, isExpanded: (value: Bool, toggle: () -> Void)?, @@ -52,7 +52,7 @@ final class TextProcessingTextAreaComponent: Component { self.context = context self.theme = theme self.strings = strings - self.titlePrefix = titlePrefix + self.titleFormat = titleFormat self.isExpanded = isExpanded self.copyAction = copyAction self.title = title @@ -75,7 +75,7 @@ final class TextProcessingTextAreaComponent: Component { if lhs.strings !== rhs.strings { return false } - if lhs.titlePrefix != rhs.titlePrefix { + if lhs.titleFormat != rhs.titleFormat { return false } if lhs.title != rhs.title { @@ -110,7 +110,6 @@ final class TextProcessingTextAreaComponent: Component { private weak var state: EmptyComponentState? private var isUpdating: Bool = false - private let titlePrefix = ComponentView() private let title = ComponentView() private var titleArrow: ComponentView? private var emojify: ComponentView? @@ -225,40 +224,32 @@ final class TextProcessingTextAreaComponent: Component { var contentHeight: CGFloat = 0.0 contentHeight += topInset - let titlePrefixSize = self.titlePrefix.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: component.titlePrefix, font: Font.semibold(13.0), textColor: component.theme.list.itemSecondaryTextColor)) - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 10.0, height: 100.0) - ) + let titleString = NSMutableAttributedString() + if let range = component.titleFormat.range(of: "{}") { + if range.lowerBound != component.titleFormat.startIndex { + titleString.append(NSAttributedString(string: String(component.titleFormat[component.titleFormat.startIndex ..< range.lowerBound]), font: Font.semibold(13.0), textColor: component.theme.list.itemSecondaryTextColor)) + } + titleString.append(NSAttributedString(string: component.title, font: Font.semibold(13.0), textColor: component.theme.list.itemAccentColor)) + if range.upperBound != component.titleFormat.endIndex { + titleString.append(NSAttributedString(string: String(component.titleFormat[range.upperBound...]), font: Font.semibold(13.0), textColor: component.theme.list.itemSecondaryTextColor)) + } + } else { + titleString.append(NSAttributedString(string: component.titleFormat, font: Font.semibold(13.0), textColor: component.theme.list.itemSecondaryTextColor)) + } + let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: component.title, font: Font.semibold(13.0), textColor: component.theme.list.itemAccentColor)) + text: .plain(titleString) )), environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 10.0, height: 100.0) ) - let titlePrefixFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: titlePrefixSize) - var titleFrame = CGRect(origin: CGPoint(x: titlePrefixFrame.maxX, y: titlePrefixFrame.minY), size: titleSize) - if !component.titlePrefix.isEmpty { - titleFrame.origin.x += 3.0 - } + let titleFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: titleSize) transition.setFrame(view: self.titleButton, frame: titleFrame.insetBy(dx: -10.0, dy: -10.0)) - if let titlePrefixView = self.titlePrefix.view { - if titlePrefixView.superview == nil { - titlePrefixView.layer.anchorPoint = CGPoint() - titlePrefixView.isUserInteractionEnabled = false - self.addSubview(titlePrefixView) - } - titlePrefixView.bounds = CGRect(origin: CGPoint(), size: titlePrefixFrame.size) - transition.setPosition(view: titlePrefixView, position: titlePrefixFrame.origin) - } if let titleView = self.title.view { if titleView.superview == nil { titleView.layer.anchorPoint = CGPoint() @@ -332,7 +323,7 @@ final class TextProcessingTextAreaComponent: Component { selected: emojifyValue.value ))), AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "Emojify", font: Font.semibold(13.0), textColor: component.theme.list.itemSecondaryTextColor)) + text: .plain(NSAttributedString(string: component.strings.TextProcessing_Emojify, font: Font.semibold(13.0), textColor: component.theme.list.itemSecondaryTextColor)) ))) ], spacing: 7.0)), effectAlignment: .center, @@ -516,7 +507,7 @@ final class TextProcessingTextAreaComponent: Component { transition: expandButtonTransition, component: AnyComponent(PlainButtonComponent( content: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: isExpanded.value ? "less" : "more", font: Font.regular(17.0), textColor: component.theme.list.itemAccentColor)) + text: .plain(NSAttributedString(string: isExpanded.value ? component.strings.TextProcessing_TextCollapse : component.strings.TextProcessing_TextExpand, font: Font.regular(17.0), textColor: component.theme.list.itemAccentColor)) )), effectAlignment: .right, action: { diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingTranslateContentComponent.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingTranslateContentComponent.swift index 31f7ed3fcb..5fd3943ee1 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingTranslateContentComponent.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingTranslateContentComponent.swift @@ -14,18 +14,39 @@ import TooltipComponent private let languageRecognizer = NLLanguageRecognizer() -func localizedLanguageName(strings: PresentationStrings, language: String) -> String { - let toLang = language - let key = "Translation.Language.\(toLang)" - let translateTitle: String - if let string = strings.primaryComponent.dict[key] { - translateTitle = string - } else { - let languageLocale = Locale(identifier: language) - let toLanguage = languageLocale.localizedString(forLanguageCode: toLang)?.capitalized ?? "" - return toLanguage +enum LocalizedLanguageNameKind { + case neutral + case translateFrom + case translateTo +} + +func localizedLanguageName(strings: PresentationStrings, language: String, kind: LocalizedLanguageNameKind) -> String { + var result: String? + + switch kind { + case .neutral: + if let value = strings.primaryComponent.dict["Translation.Language.\(language)"] { + result = value + } + case .translateFrom: + if let value = strings.primaryComponent.dict["Translation.LanguageFrom.\(language)"] { + result = value + } + case .translateTo: + if let value = strings.primaryComponent.dict["Translation.LanguageTo.\(language)"] { + result = value + } } - return translateTitle + if result == nil { + if let value = strings.primaryComponent.dict["Translation.Language.\(language)"] { + result = value + } + } + if result == nil { + let languageLocale = Locale(identifier: language) + result = languageLocale.localizedString(forLanguageCode: language)?.capitalized + } + return result ?? "" } final class TextProcessingTranslateContentComponent: Component { @@ -139,6 +160,8 @@ final class TextProcessingTranslateContentComponent: Component { final class View: UIView { private let sourceText = ComponentView() + private let styleSelection = ComponentView() + private let styleSelectionContainer: UIView private let targetText = ComponentView() private let separatorLayer: SimpleLayer @@ -152,6 +175,9 @@ final class TextProcessingTranslateContentComponent: Component { override init(frame: CGRect) { self.separatorLayer = SimpleLayer() + self.styleSelectionContainer = SparseContainerView() + self.styleSelectionContainer.clipsToBounds = true + self.styleSelectionContainer.layer.cornerRadius = 26.0 super.init(frame: frame) @@ -277,37 +303,34 @@ final class TextProcessingTranslateContentComponent: Component { let bottomInset: CGFloat = 14.0 let blockSpacing: CGFloat = 30.0 - let fromPrefix: String - let toPrefix: String + let fromFormat: String + let toFormat: String var toTitle: String switch component.mode { case .translate: - fromPrefix = "From" - toPrefix = "To" - toTitle = localizedLanguageName(strings: component.strings, language: component.externalState.result?.language ?? "") - if component.externalState.style != .neutral { - toTitle.append(" (") - let styleName = localizedStyleName(strings: component.strings, styleId: component.externalState.style) - toTitle.append(styleName) - toTitle.append(")") + fromFormat = component.strings.TextProcessing_Translate_FromLanguage + toFormat = component.strings.TextProcessing_Translate_ToLanguage + toTitle = localizedLanguageName(strings: component.strings, language: component.externalState.result?.language ?? "", kind: .translateTo) + if case .style = component.externalState.style { + toTitle = component.strings.TextProcessing_Translate_LanguageStyle(toTitle, localizedStyleName(strings: component.strings, styleId: component.externalState.style)).string } case .stylize, .fix: - fromPrefix = "Original:" + fromFormat = component.strings.TextProcessing_OriginalBadge if case .stylize = component.mode { if component.externalState.style == .neutral { - toPrefix = "Original" + toFormat = component.strings.TextProcessing_OriginalStyleBadge } else { - toPrefix = "Result" + toFormat = component.strings.TextProcessing_ResultBadge } } else { - toPrefix = "Result" + toFormat = component.strings.TextProcessing_ResultBadge } toTitle = "" } contentHeight += topInset if case .stylize = component.mode { - let sourceTextSize = self.sourceText.update( + let styleSelectionSize = self.styleSelection.update( transition: transition, component: AnyComponent(TextProcessingStyleSelectionComponent( context: component.context, @@ -335,15 +358,16 @@ final class TextProcessingTranslateContentComponent: Component { environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 46.0) ) - let sourceTextFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: sourceTextSize) - contentHeight += sourceTextSize.height + let styleSelectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: styleSelectionSize) + contentHeight += styleSelectionSize.height - if let sourceTextView = self.sourceText.view { - if sourceTextView.superview == nil { - self.sourceText.parentState = state - self.addSubview(sourceTextView) + if let styleSelectionView = self.styleSelection.view { + if styleSelectionView.superview == nil { + self.styleSelection.parentState = state + self.styleSelectionContainer.addSubview(styleSelectionView) + self.addSubview(self.styleSelectionContainer) } - transition.setFrame(view: sourceTextView, frame: sourceTextFrame) + transition.setFrame(view: styleSelectionView, frame: styleSelectionFrame) } } else { let sourceTextSize = self.sourceText.update( @@ -352,8 +376,8 @@ final class TextProcessingTranslateContentComponent: Component { context: component.context, theme: component.theme, strings: component.strings, - titlePrefix: fromPrefix, - title: localizedLanguageName(strings: component.strings, language: component.externalState.sourceLanguage ?? ""), + titleFormat: fromFormat, + title: localizedLanguageName(strings: component.strings, language: component.externalState.sourceLanguage ?? "", kind: .translateFrom), titleAction: nil, isExpanded: ( component.externalState.isSourceTextExpanded, @@ -397,7 +421,7 @@ final class TextProcessingTranslateContentComponent: Component { context: component.context, theme: component.theme, strings: component.strings, - titlePrefix: toPrefix, + titleFormat: toFormat, title: toTitle, titleAction: component.mode == .translate ? { [weak self] sourceView in guard let self, let component = self.component, let result = component.externalState.result else { @@ -494,7 +518,7 @@ final class TextProcessingTranslateContentComponent: Component { transition: tooltipTransition, component: AnyComponent(TooltipComponent( content: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "Select Style", font: Font.regular(15.0), textColor: .white)) + text: .plain(NSAttributedString(string: component.strings.TextProcessing_StyleTooltip, font: Font.regular(15.0), textColor: .white)) )) )), environment: {}, @@ -521,6 +545,8 @@ final class TextProcessingTranslateContentComponent: Component { } } } + + transition.setFrame(view: self.styleSelectionContainer, frame: CGRect(origin: CGPoint(), size: size)) return size } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index 9e0e64b24f..5a9e1e9a78 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -1611,10 +1611,9 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() var menuItems: [ContextMenuItem] = [] - //TODO:localize menuItems.append(.action(ContextMenuActionItem( id: nil, - text: "Read All Poll Votes", + text: strongSelf.presentationData.strings.Conversation_ReadAllPollVotes, textColor: .primary, textLayout: .singleLine, icon: { theme in diff --git a/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift index 0ac7851856..9dcafa3e61 100644 --- a/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift @@ -57,8 +57,7 @@ private enum ChatReportPeerTitleButton: Equatable { case .restartTopic: return strings.Chat_PanelRestartTopic case .setPhoto: - //TODO:localize - return "Set Profile Photo" + return strings.Chat_PanelSetBotPhoto } } }