| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/browser/ui/settings/language/language_settings_mediator.h" |
| |
| #import <memory> |
| |
| #import "base/apple/foundation_util.h" |
| #import "base/check.h" |
| #import "base/containers/contains.h" |
| #import "base/metrics/histogram_macros.h" |
| #import "base/notreached.h" |
| #import "base/strings/sys_string_conversions.h" |
| #import "components/language/core/browser/language_model_manager.h" |
| #import "components/language/core/browser/pref_names.h" |
| #import "components/language/core/common/language_util.h" |
| #import "components/prefs/ios/pref_observer_bridge.h" |
| #import "components/prefs/pref_change_registrar.h" |
| #import "components/prefs/pref_service.h" |
| #import "components/translate/core/browser/translate_pref_names.h" |
| #import "components/translate/core/browser/translate_prefs.h" |
| #import "ios/chrome/browser/shared/model/application_context/application_context.h" |
| #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h" |
| #import "ios/chrome/browser/translate/chrome_ios_translate_client.h" |
| #import "ios/chrome/browser/translate/translate_service_ios.h" |
| #import "ios/chrome/browser/ui/settings/language/cells/language_item.h" |
| #import "ios/chrome/browser/ui/settings/language/language_settings_consumer.h" |
| #import "ios/chrome/browser/ui/settings/language/language_settings_histograms.h" |
| #import "ios/chrome/grit/ios_strings.h" |
| #import "ui/base/l10n/l10n_util_mac.h" |
| |
| @interface LanguageSettingsMediator () <PrefObserverDelegate> { |
| // Registrar for pref change notifications. |
| std::unique_ptr<PrefChangeRegistrar> _prefChangeRegistrar; |
| |
| // Pref observer to track changes to translate::prefs::kOfferTranslateEnabled. |
| std::unique_ptr<PrefObserverBridge> _offerTranslatePrefObserverBridge; |
| |
| // Pref observer to track changes to language::prefs::kAcceptLanguages. |
| std::unique_ptr<PrefObserverBridge> _acceptLanguagesPrefObserverBridge; |
| |
| // Pref observer to track changes to prefs::kBlockedLanguages. |
| std::unique_ptr<PrefObserverBridge> _blockedLanguagesPrefObserverBridge; |
| |
| // Translate wrapper for the PrefService. |
| std::unique_ptr<translate::TranslatePrefs> _translatePrefs; |
| } |
| |
| // The LanguageModelManager passed to this instance. |
| @property(nonatomic, assign) |
| language::LanguageModelManager* languageModelManager; |
| // The PrefService passed to this instance. |
| @property(nonatomic, assign) PrefService* prefService; |
| |
| @end |
| |
| @implementation LanguageSettingsMediator |
| |
| @synthesize consumer = _consumer; |
| |
| - (instancetype)initWithLanguageModelManager: |
| (language::LanguageModelManager*)languageModelManager |
| prefService:(PrefService*)prefService { |
| self = [super init]; |
| if (self) { |
| _languageModelManager = languageModelManager; |
| _prefService = prefService; |
| |
| _prefChangeRegistrar = std::make_unique<PrefChangeRegistrar>(); |
| _prefChangeRegistrar->Init(self.prefService); |
| _offerTranslatePrefObserverBridge = |
| std::make_unique<PrefObserverBridge>(self); |
| _offerTranslatePrefObserverBridge->ObserveChangesForPreference( |
| translate::prefs::kOfferTranslateEnabled, _prefChangeRegistrar.get()); |
| _acceptLanguagesPrefObserverBridge = |
| std::make_unique<PrefObserverBridge>(self); |
| _acceptLanguagesPrefObserverBridge->ObserveChangesForPreference( |
| language::prefs::kAcceptLanguages, _prefChangeRegistrar.get()); |
| _blockedLanguagesPrefObserverBridge = |
| std::make_unique<PrefObserverBridge>(self); |
| _blockedLanguagesPrefObserverBridge->ObserveChangesForPreference( |
| translate::prefs::kBlockedLanguages, _prefChangeRegistrar.get()); |
| |
| _translatePrefs = |
| ChromeIOSTranslateClient::CreateTranslatePrefs(self.prefService); |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| // In case this has not been explicitly called. |
| [self stopObservingModel]; |
| _languageModelManager = nullptr; |
| _prefService = nullptr; |
| } |
| |
| #pragma mark - PrefObserverDelegate |
| |
| // Called when the value of translate::prefs::kOfferTranslateEnabled, |
| // language::prefs::kAcceptLanguages or |
| // translate::prefs::kBlockedLanguages change. |
| - (void)onPreferenceChanged:(const std::string&)preferenceName { |
| DCHECK(preferenceName == translate::prefs::kOfferTranslateEnabled || |
| preferenceName == language::prefs::kAcceptLanguages || |
| preferenceName == translate::prefs::kBlockedLanguages); |
| |
| // Inform the consumer. |
| if (preferenceName == translate::prefs::kOfferTranslateEnabled) { |
| [self.consumer translateEnabled:[self translateEnabled]]; |
| } else { |
| [self.consumer languagePrefsChanged]; |
| } |
| } |
| |
| #pragma mark - LanguageSettingsDataSource |
| |
| - (NSArray<LanguageItem*>*)acceptLanguagesItems { |
| // Create a map of supported language codes to supported languages. |
| std::vector<translate::TranslateLanguageInfo> supportedLanguages; |
| translate::TranslatePrefs::GetLanguageInfoList( |
| GetApplicationContext()->GetApplicationLocale(), |
| _translatePrefs->IsTranslateAllowedByPolicy(), &supportedLanguages); |
| std::map<std::string, translate::TranslateLanguageInfo> supportedLanguagesMap; |
| for (const auto& supportedLanguage : supportedLanguages) { |
| supportedLanguagesMap[supportedLanguage.code] = supportedLanguage; |
| } |
| |
| // Get the accept languages. |
| std::vector<std::string> languageCodes; |
| _translatePrefs->GetLanguageList(&languageCodes); |
| |
| NSMutableArray<LanguageItem*>* acceptLanguages = |
| [NSMutableArray arrayWithCapacity:languageCodes.size()]; |
| for (const auto& languageCode : languageCodes) { |
| // Ignore unsupported languages. |
| auto it = supportedLanguagesMap.find(languageCode); |
| if (it == supportedLanguagesMap.end()) { |
| // languageCodes comes from a synced pref and may contain language codes |
| // that are not supported on the platform, or on this device locale as |
| // defined by the GetLanguageInfoList above. |
| // Ignore them. |
| // TODO(crbug.com/1430745): Investigate why this happens and how to |
| // reconcile data. |
| continue; |
| } |
| const translate::TranslateLanguageInfo& language = it->second; |
| LanguageItem* languageItem = [self languageItemFromLanguage:language]; |
| |
| // Language codes used in the language settings have the Chrome internal |
| // format while the Translate target language has the Translate server |
| // format. To convert the former to the latter the utilily function |
| // ToTranslateLanguageSynonym() must be used. |
| std::string canonicalLanguageCode = languageItem.languageCode; |
| language::ToTranslateLanguageSynonym(&canonicalLanguageCode); |
| std::string targetLanguageCode = TranslateServiceIOS::GetTargetLanguage( |
| self.prefService, self.languageModelManager->GetPrimaryModel()); |
| languageItem.targetLanguage = targetLanguageCode == canonicalLanguageCode; |
| |
| // A language is Translate-blocked if the language is not supported by the |
| // Translate server, or user is fluent in the language, or it is the |
| // Translate target language. |
| languageItem.blocked = |
| !languageItem.supportsTranslate || |
| _translatePrefs->IsBlockedLanguage(languageItem.languageCode) || |
| [languageItem isTargetLanguage]; |
| |
| if ([self translateEnabled]) { |
| // Show a disclosure indicator to suggest language details are available |
| // as well as a label indicating if the language is Translate-blocked. |
| languageItem.accessibilityTraits |= UIAccessibilityTraitButton; |
| languageItem.accessoryType = UITableViewCellAccessoryDisclosureIndicator; |
| languageItem.trailingDetailText = |
| [languageItem isBlocked] |
| ? l10n_util::GetNSString( |
| IDS_IOS_LANGUAGE_SETTINGS_NEVER_TRANSLATE_TITLE) |
| : l10n_util::GetNSString( |
| IDS_IOS_LANGUAGE_SETTINGS_OFFER_TO_TRANSLATE_TITLE); |
| } |
| [acceptLanguages addObject:languageItem]; |
| } |
| return acceptLanguages; |
| } |
| |
| - (NSArray<LanguageItem*>*)supportedLanguagesItems { |
| // Get the accept languages. |
| std::vector<std::string> acceptLanguageCodes; |
| _translatePrefs->GetLanguageList(&acceptLanguageCodes); |
| |
| // Get the supported languages. |
| std::vector<translate::TranslateLanguageInfo> languages; |
| translate::TranslatePrefs::GetLanguageInfoList( |
| GetApplicationContext()->GetApplicationLocale(), |
| _translatePrefs->IsTranslateAllowedByPolicy(), &languages); |
| |
| NSMutableArray<LanguageItem*>* supportedLanguages = |
| [NSMutableArray arrayWithCapacity:languages.size()]; |
| for (const auto& language : languages) { |
| // Ignore languages already in the accept languages list. |
| if (base::Contains(acceptLanguageCodes, language.code)) { |
| continue; |
| } |
| LanguageItem* languageItem = [self languageItemFromLanguage:language]; |
| languageItem.accessibilityTraits |= UIAccessibilityTraitButton; |
| [supportedLanguages addObject:languageItem]; |
| } |
| return supportedLanguages; |
| } |
| |
| - (BOOL)translateEnabled { |
| return self.prefService->GetBoolean(translate::prefs::kOfferTranslateEnabled); |
| } |
| |
| - (BOOL)translateManaged { |
| return self.prefService->IsManagedPreference( |
| translate::prefs::kOfferTranslateEnabled); |
| } |
| |
| - (void)stopObservingModel { |
| _offerTranslatePrefObserverBridge.reset(); |
| _acceptLanguagesPrefObserverBridge.reset(); |
| _blockedLanguagesPrefObserverBridge.reset(); |
| _prefChangeRegistrar.reset(); |
| _translatePrefs.reset(); |
| } |
| |
| #pragma mark - LanguageSettingsCommands |
| |
| - (void)setTranslateEnabled:(BOOL)enabled { |
| self.prefService->SetBoolean(translate::prefs::kOfferTranslateEnabled, |
| enabled); |
| |
| UMA_HISTOGRAM_ENUMERATION( |
| kLanguageSettingsActionsHistogram, |
| enabled ? LanguageSettingsActions::ENABLE_TRANSLATE_GLOBALLY |
| : LanguageSettingsActions::DISABLE_TRANSLATE_GLOBALLY); |
| } |
| |
| - (void)moveLanguage:(const std::string&)languageCode |
| downward:(BOOL)downward |
| withOffset:(NSUInteger)offset { |
| translate::TranslatePrefs::RearrangeSpecifier where = |
| downward ? translate::TranslatePrefs::kDown |
| : translate::TranslatePrefs::kUp; |
| std::vector<std::string> languageCodes; |
| _translatePrefs->GetLanguageList(&languageCodes); |
| _translatePrefs->RearrangeLanguage(languageCode, where, offset, |
| languageCodes); |
| |
| UMA_HISTOGRAM_ENUMERATION(kLanguageSettingsActionsHistogram, |
| LanguageSettingsActions::LANGUAGE_LIST_REORDERED); |
| } |
| |
| - (void)addLanguage:(const std::string&)languageCode { |
| _translatePrefs->AddToLanguageList(languageCode, /*force_blocked=*/false); |
| |
| UMA_HISTOGRAM_ENUMERATION(kLanguageSettingsActionsHistogram, |
| LanguageSettingsActions::LANGUAGE_ADDED); |
| } |
| |
| - (void)removeLanguage:(const std::string&)languageCode { |
| _translatePrefs->RemoveFromLanguageList(languageCode); |
| |
| UMA_HISTOGRAM_ENUMERATION(kLanguageSettingsActionsHistogram, |
| LanguageSettingsActions::LANGUAGE_REMOVED); |
| } |
| |
| - (void)blockLanguage:(const std::string&)languageCode { |
| _translatePrefs->BlockLanguage(languageCode); |
| |
| UMA_HISTOGRAM_ENUMERATION( |
| kLanguageSettingsActionsHistogram, |
| LanguageSettingsActions::DISABLE_TRANSLATE_FOR_SINGLE_LANGUAGE); |
| } |
| |
| - (void)unblockLanguage:(const std::string&)languageCode { |
| _translatePrefs->UnblockLanguage(languageCode); |
| |
| UMA_HISTOGRAM_ENUMERATION( |
| kLanguageSettingsActionsHistogram, |
| LanguageSettingsActions::ENABLE_TRANSLATE_FOR_SINGLE_LANGUAGE); |
| } |
| |
| #pragma mark - Private methods |
| |
| - (LanguageItem*)languageItemFromLanguage: |
| (const translate::TranslateLanguageInfo&)language { |
| LanguageItem* languageItem = [[LanguageItem alloc] init]; |
| languageItem.languageCode = language.code; |
| languageItem.text = base::SysUTF8ToNSString(language.display_name); |
| languageItem.leadingDetailText = |
| base::SysUTF8ToNSString(language.native_display_name); |
| languageItem.supportsTranslate = language.supports_translate; |
| return languageItem; |
| } |
| |
| @end |