diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift index cf145df25e0ac..0ac746507eb76 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift @@ -3047,7 +3047,21 @@ class BrowserViewController: UIViewController, if data.isTranslated, let langCode = data.translatedToLanguage { configureShowOriginalHeader(for: alert, languageCode: langCode) } else { - alert.title = .Translations.LanguagePicker.Title + let title: String = .Translations.LanguagePicker.Title + let attributedTitleKey = "attributedTitle" + alert.title = title + alert.setValue( + NSAttributedString( + string: title, + attributes: [ + .font: DefaultDynamicFontHelper.preferredBoldFont( + withTextStyle: .headline, + size: UIFont.labelFontSize + ) + ] + ), + forKey: attributedTitleKey + ) } data.languages.forEach { code in @@ -3076,15 +3090,17 @@ class BrowserViewController: UIViewController, )) }) - if sourceButton == nil { - alert.addAction(UIAlertAction(title: .CancelString, style: .default)) + if #available(iOS 26, *), sourceButton != nil { + } else { + alert.addAction(UIAlertAction(title: .CancelString, style: .cancel)) } if let popover = alert.popoverPresentationController { if let sourceButton { popover.sourceView = sourceButton popover.sourceRect = sourceButton.bounds - } else { + popover.permittedArrowDirections = [.up, .down] + } else if #unavailable(iOS 26) { popover.sourceView = view popover.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0) popover.permittedArrowDirections = [] diff --git a/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuCoordinator.swift b/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuCoordinator.swift index 53c0c506280cc..b102a147af1d2 100644 --- a/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuCoordinator.swift +++ b/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuCoordinator.swift @@ -178,7 +178,9 @@ class MainMenuCoordinator: BaseCoordinator, FeatureFlaggable { let manager = PreferredTranslationLanguagesManager(prefs: prefs) let supported = await ASTranslationModelsFetcher.shared.fetchSupportedTargetLanguages() let languages = manager.preferredLanguages(supportedTargetLanguages: supported) - if isSingleLanguageFlow, let language = languages.first, !isTranslated { + let pageLanguage = try? await TranslationsService().detectPageLanguage(for: windowUUID) + let filteredLanguages = languages.filter { $0 != pageLanguage } + if isSingleLanguageFlow, let language = filteredLanguages.first, !isTranslated { store.dispatch(TranslationLanguageSelectedAction( windowUUID: windowUUID, targetLanguage: language, @@ -186,7 +188,7 @@ class MainMenuCoordinator: BaseCoordinator, FeatureFlaggable { )) } else { store.dispatch(GeneralBrowserAction( - translationLanguages: languages, + translationLanguages: filteredLanguages, isPageTranslated: isTranslated, translatedToLanguage: translatedLanguage, windowUUID: windowUUID, diff --git a/firefox-ios/Client/Frontend/Translations/Service/TranslationsService.swift b/firefox-ios/Client/Frontend/Translations/Service/TranslationsService.swift index abf174cc97054..4dd86fb3ae86c 100644 --- a/firefox-ios/Client/Frontend/Translations/Service/TranslationsService.swift +++ b/firefox-ios/Client/Frontend/Translations/Service/TranslationsService.swift @@ -86,7 +86,7 @@ final class TranslationsService: TranslationsServiceProtocol { /// Attempts to detect the language of the currently displayed page. /// Returns a BCP-47 language tag (e.g. "en", "fr") on success. /// Otherwise throws a typed `TranslationsServiceError`. - private func detectPageLanguage(for windowUUID: WindowUUID) async throws -> String { + func detectPageLanguage(for windowUUID: WindowUUID) async throws -> String { let webView = try currentWebView(for: windowUUID) let source = WebViewLanguageSampleSource(webView: webView) do { diff --git a/firefox-ios/Client/Frontend/Translations/Service/TranslationsServiceProtocol.swift b/firefox-ios/Client/Frontend/Translations/Service/TranslationsServiceProtocol.swift index 5fb4f0aedd058..432912fde2d6c 100644 --- a/firefox-ios/Client/Frontend/Translations/Service/TranslationsServiceProtocol.swift +++ b/firefox-ios/Client/Frontend/Translations/Service/TranslationsServiceProtocol.swift @@ -28,4 +28,6 @@ protocol TranslationsServiceProtocol { func discardTranslations(for windowUUID: WindowUUID) async throws /// Returns the unique set of languages that can be used as translation targets. func fetchSupportedTargetLanguages() async -> [String] + /// Returns the BCP-47 language code of the currently displayed page (e.g. "ja", "en"). + func detectPageLanguage(for windowUUID: WindowUUID) async throws -> String } diff --git a/firefox-ios/Client/Frontend/Translations/TranslationsMiddleware.swift b/firefox-ios/Client/Frontend/Translations/TranslationsMiddleware.swift index ecc24a3ba80d9..c210633987e17 100644 --- a/firefox-ios/Client/Frontend/Translations/TranslationsMiddleware.swift +++ b/firefox-ios/Client/Frontend/Translations/TranslationsMiddleware.swift @@ -109,12 +109,22 @@ final class TranslationsMiddleware: FeatureFlaggable { let manager = PreferredTranslationLanguagesManager(prefs: profile.prefs) let supported = await translationsService.fetchSupportedTargetLanguages() let languages = manager.preferredLanguages(supportedTargetLanguages: supported) - store.dispatch(GeneralBrowserAction( - buttonTapped: capturedButton, - translationLanguages: languages, - windowUUID: action.windowUUID, - actionType: GeneralBrowserActionType.showTranslationLanguagePicker - )) + let pageLanguage = try? await translationsService.detectPageLanguage(for: action.windowUUID) + let filteredLanguages = languages.filter { $0 != pageLanguage } + if !translationConfiguration.isMultiLanguageFlow, let singleLanguage = filteredLanguages.first { + store.dispatch(TranslationLanguageSelectedAction( + windowUUID: action.windowUUID, + targetLanguage: singleLanguage, + actionType: TranslationsActionType.didSelectTargetLanguage + )) + } else { + store.dispatch(GeneralBrowserAction( + buttonTapped: capturedButton, + translationLanguages: filteredLanguages, + windowUUID: action.windowUUID, + actionType: GeneralBrowserActionType.showTranslationLanguagePicker + )) + } } } else if translationConfiguration.state == .inactive { guard let deviceLanguage = Locale.current.languageCode else { return } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TranslationsTests/MockTranslationsService.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TranslationsTests/MockTranslationsService.swift index b6bed6371eaeb..d24145ec45e41 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TranslationsTests/MockTranslationsService.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TranslationsTests/MockTranslationsService.swift @@ -13,18 +13,21 @@ final class MockTranslationsService: TranslationsServiceProtocol { private let translateResult: Result private let firstResponseReceivedResult: Result private let discardResult: Result + private let detectPageLanguageResult: Result // MARK: - Init init( shouldOfferTranslationResult: Result = .success(false), translateResult: Result = .success(()), firstResponseReceivedResult: Result = .success(()), - discardResult: Result = .success(()) + discardResult: Result = .success(()), + detectPageLanguageResult: Result = .success("en") ) { self.shouldOfferTranslationResult = shouldOfferTranslationResult self.translateResult = translateResult self.firstResponseReceivedResult = firstResponseReceivedResult self.discardResult = discardResult + self.detectPageLanguageResult = detectPageLanguageResult } // MARK: - TranslationsServiceProtocol @@ -52,4 +55,8 @@ final class MockTranslationsService: TranslationsServiceProtocol { func fetchSupportedTargetLanguages() async -> [String] { return [] } + + func detectPageLanguage(for windowUUID: WindowUUID) async throws -> String { + return try detectPageLanguageResult.get() + } }