From 8a820da5ad2d086e431925066a1f2fa86c94de8e Mon Sep 17 00:00:00 2001 From: Manuel Martin Date: Fri, 14 Feb 2020 18:47:39 +0100 Subject: [PATCH] Follow system language (#2781) * Languages refactor * Fixes first launch crash * Stop using a hardcoded locale in the sumo pages * Fix capitalizing string exception. Co-authored-by: Daosheng Mu --- .../mozilla/vrbrowser/VRBrowserActivity.java | 18 +- .../vrbrowser/VRBrowserApplication.java | 10 +- .../browser/engine/SessionStore.java | 2 +- .../vrbrowser/ui/adapters/Language.java | 37 +- .../ui/adapters/LanguagesAdapter.java | 2 +- .../ui/viewmodel/WindowViewModel.java | 46 +- .../vrbrowser/ui/views/NavigationURLBar.java | 4 - .../vrbrowser/ui/widgets/TrayWidget.java | 2 + .../ui/widgets/dialogs/VoiceSearchWidget.java | 2 +- .../settings/ContentLanguageOptionsView.java | 26 +- .../settings/DisplayLanguageOptionsView.java | 39 +- .../widgets/settings/LanguageOptionsView.java | 6 +- .../VoiceSearchLanguageOptionsView.java | 33 +- .../mozilla/vrbrowser/utils/LocaleUtils.java | 500 ++++++++++-------- .../mozilla/vrbrowser/utils/StringUtils.java | 20 +- app/src/main/res/layout/language_item.xml | 2 +- app/src/main/res/layout/navigation_url.xml | 2 +- app/src/main/res/values/non_L10n.xml | 6 +- app/src/main/res/values/strings.xml | 4 + 19 files changed, 397 insertions(+), 364 deletions(-) diff --git a/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java b/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java index 559741b41..c24a47171 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java @@ -58,6 +58,7 @@ import org.mozilla.vrbrowser.telemetry.GleanMetricsService; import org.mozilla.vrbrowser.telemetry.TelemetryWrapper; import org.mozilla.vrbrowser.ui.OffscreenDisplay; +import org.mozilla.vrbrowser.ui.adapters.Language; import org.mozilla.vrbrowser.ui.widgets.KeyboardWidget; import org.mozilla.vrbrowser.ui.widgets.NavigationBarWidget; import org.mozilla.vrbrowser.ui.widgets.RootWidget; @@ -86,7 +87,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; -import java.util.Locale; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; @@ -210,7 +210,8 @@ public void onGlobalFocusChanged(View oldFocus, View newFocus) { @Override protected void attachBaseContext(Context base) { - super.attachBaseContext(LocaleUtils.setLocale(base)); + Context newContext = LocaleUtils.init(base); + super.attachBaseContext(newContext); } @Override @@ -230,8 +231,6 @@ protected void onCreate(Bundle savedInstanceState) { // Set a global exception handler as soon as possible GlobalExceptionHandler.register(this.getApplicationContext()); - LocaleUtils.init(); - if (DeviceType.isOculusBuild()) { workaroundGeckoSigAction(); } @@ -243,7 +242,7 @@ protected void onCreate(Bundle savedInstanceState) { SessionStore.get().setContext(this, extras); SessionStore.get().initializeServices(); SessionStore.get().initializeStores(this); - SessionStore.get().setLocales(LocaleUtils.getPreferredLocales(this)); + SessionStore.get().setLocales(LocaleUtils.getPreferredLanguageTags(this)); // Create broadcast receiver for getting crash messages from crash process IntentFilter intentFilter = new IntentFilter(); @@ -508,9 +507,12 @@ protected void onNewIntent(final Intent intent) { @Override public void onConfigurationChanged(Configuration newConfig) { + Language language = LocaleUtils.getDisplayLanguage(this); + newConfig.setLocale(language.getLocale()); getBaseContext().getResources().updateConfiguration(newConfig, getBaseContext().getResources().getDisplayMetrics()); - LocaleUtils.refresh(); + LocaleUtils.update(this, language); + mWidgets.forEach((i, widget) -> widget.onConfigurationChanged(newConfig)); SessionStore.get().onConfigurationChanged(newConfig); @@ -1527,9 +1529,7 @@ public void saveState() { @Override public void updateLocale(@NonNull Context context) { - Configuration configuration = context.getResources().getConfiguration(); - configuration.setLocale(Locale.forLanguageTag(LocaleUtils.getDisplayLanguage(this).getId())); - onConfigurationChanged(new Configuration(context.getResources().getConfiguration())); + onConfigurationChanged(context.getResources().getConfiguration()); } private native void addWidgetNative(int aHandle, WidgetPlacement aPlacement); diff --git a/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserApplication.java b/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserApplication.java index f94cb5811..8c86c8a6e 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserApplication.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserApplication.java @@ -16,6 +16,7 @@ import org.mozilla.vrbrowser.db.DataRepository; import org.mozilla.vrbrowser.telemetry.GleanMetricsService; import org.mozilla.vrbrowser.telemetry.TelemetryWrapper; +import org.mozilla.vrbrowser.ui.adapters.Language; import org.mozilla.vrbrowser.utils.BitmapCache; import org.mozilla.vrbrowser.utils.LocaleUtils; @@ -43,15 +44,16 @@ public void onCreate() { @Override protected void attachBaseContext(Context base) { - LocaleUtils.saveSystemLocale(); - super.attachBaseContext(LocaleUtils.setLocale(base)); + Context context = LocaleUtils.init(base); + super.attachBaseContext(context); } @Override public void onConfigurationChanged(Configuration newConfig) { + Context context = LocaleUtils.init(this); + Language language = LocaleUtils.getDisplayLanguage(context); + newConfig.setLocale(language.getLocale()); super.onConfigurationChanged(newConfig); - LocaleUtils.setDisplayLocale(this, newConfig.getLocales().get(0).toLanguageTag()); - LocaleUtils.setLocale(this); } public Services getServices() { diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStore.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStore.java index 3c55588a0..cc580eb9a 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStore.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStore.java @@ -279,7 +279,7 @@ public void setRemoteDebugging(final boolean enabled) { public void setLocales(List locales) { if (mRuntime != null) { - mRuntime.getSettings().setLocales(locales.stream().toArray(String[]::new)); + mRuntime.getSettings().setLocales(locales.toArray(new String[0])); } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/Language.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/Language.java index 3f606633d..8b3a7233d 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/Language.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/Language.java @@ -1,23 +1,40 @@ package org.mozilla.vrbrowser.ui.adapters; +import org.mozilla.vrbrowser.utils.StringUtils; + +import java.util.Locale; + public class Language { - public Language(String id, String name) { - this.id = id; - this.name = name; + public Language(Locale locale) { + this.locale = locale; + this.isPreferred = false; + + String languageId = locale.toLanguageTag(); + String displayName = StringUtils.capitalize(locale.getDisplayName()); + this.displayName = displayName + " [" + languageId + "]"; + } + + public Language(Locale locale, String displayName) { + this.locale = locale; this.isPreferred = false; + this.displayName = StringUtils.capitalize(displayName); } - private String name; - private String id; + private Locale locale; private boolean isPreferred; + private String displayName; + + public Locale getLocale() { + return this.locale; + } - public String getId() { - return this.id; + public String getDisplayName() { + return this.displayName; } - public String getName() { - return this.name; + public String getLanguageTag() { + return this.locale.toLanguageTag(); } public void setPreferred(boolean isPreferred) { @@ -30,6 +47,6 @@ public boolean isPreferred() { @Override public int hashCode() { - return id.hashCode(); + return locale.hashCode(); } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/LanguagesAdapter.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/LanguagesAdapter.java index 78527c6b7..e1a790096 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/LanguagesAdapter.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/adapters/LanguagesAdapter.java @@ -72,7 +72,7 @@ public void addItem(Language language) { public void addItemAlphabetical(Language language) { int index = Collections.binarySearch(mLanguagesList, language, - (ob1, ob2) -> ob1.getName().compareToIgnoreCase(ob2.getName())); + (ob1, ob2) -> ob1.getLanguageTag().compareToIgnoreCase(ob2.getLanguageTag())); if (index < 0) { index = (index * -1) - 1; diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/viewmodel/WindowViewModel.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/viewmodel/WindowViewModel.java index daae297d7..2faff2d1f 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/viewmodel/WindowViewModel.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/viewmodel/WindowViewModel.java @@ -94,7 +94,7 @@ public WindowViewModel(Application application) { isTopBarVisible.addSource(isResizeMode, mIsTopBarVisibleObserver); isTopBarVisible.addSource(isPrivateSession, mIsTopBarVisibleObserver); isTopBarVisible.addSource(isWindowVisible, mIsTopBarVisibleObserver); - isTopBarVisible.setValue(new ObservableBoolean(false)); + isTopBarVisible.setValue(new ObservableBoolean(true)); showClearButton = new MediatorLiveData<>(); showClearButton.addSource(isOnlyWindow, mShowClearButtonObserver); @@ -160,14 +160,14 @@ public WindowViewModel(Application application) { @Override public void onChanged(ObservableBoolean o) { if (isFullscreen.getValue().get() || isResizeMode.getValue().get() || !isWindowVisible.getValue().get()) { - isTopBarVisible.setValue(new ObservableBoolean(false)); + isTopBarVisible.postValue(new ObservableBoolean(false)); } else { if (isOnlyWindow.getValue().get()) { - isTopBarVisible.setValue(new ObservableBoolean(isPrivateSession.getValue().get())); + isTopBarVisible.postValue(new ObservableBoolean(isPrivateSession.getValue().get())); } else { - isTopBarVisible.setValue(new ObservableBoolean(true)); + isTopBarVisible.postValue(new ObservableBoolean(true)); } } } @@ -176,7 +176,7 @@ public void onChanged(ObservableBoolean o) { private Observer mShowClearButtonObserver = new Observer() { @Override public void onChanged(ObservableBoolean o) { - showClearButton.setValue(new ObservableBoolean(isWindowVisible.getValue().get() && + showClearButton.postValue(new ObservableBoolean(isWindowVisible.getValue().get() && isPrivateSession.getValue().get() && isOnlyWindow.getValue().get() && !isResizeMode.getValue().get() && !isFullscreen.getValue().get())); } @@ -186,10 +186,10 @@ public void onChanged(ObservableBoolean o) { @Override public void onChanged(ObservableBoolean o) { if (isFullscreen.getValue().get() || isResizeMode.getValue().get() || isActiveWindow.getValue().get()) { - isTitleBarVisible.setValue(new ObservableBoolean(false)); + isTitleBarVisible.postValue(new ObservableBoolean(false)); } else { - isTitleBarVisible.setValue(new ObservableBoolean(isWindowVisible.getValue().get() && !isOnlyWindow.getValue().get())); + isTitleBarVisible.postValue(new ObservableBoolean(isWindowVisible.getValue().get() && !isOnlyWindow.getValue().get())); } } }; @@ -197,10 +197,10 @@ public void onChanged(ObservableBoolean o) { private Observer mIsLibraryVisibleObserver = new Observer() { @Override public void onChanged(ObservableBoolean o) { - isLibraryVisible.setValue(new ObservableBoolean(isBookmarksVisible.getValue().get() || isHistoryVisible.getValue().get())); + isLibraryVisible.postValue(new ObservableBoolean(isBookmarksVisible.getValue().get() || isHistoryVisible.getValue().get())); // We use this to force dispatch a title bar and navigation bar URL refresh when library is opened - url.setValue(url.getValue()); + url.postValue(url.getValue()); } }; @@ -236,7 +236,7 @@ public void onChanged(Spannable aUrl) { } } - titleBarUrl.setValue(UrlUtils.titleBarUrl(url)); + titleBarUrl.postValue(UrlUtils.titleBarUrl(url)); } }; @@ -250,14 +250,14 @@ public void onChanged(ObservableBoolean o) { UrlUtils.isHomeUri(getApplication(), aUrl) || isLibraryVisible.getValue().get() || UrlUtils.isBlankUri(getApplication(), aUrl)) { - isInsecureVisible.setValue(new ObservableBoolean(false)); + isInsecureVisible.postValue(new ObservableBoolean(false)); } else { - isInsecureVisible.setValue(new ObservableBoolean(true)); + isInsecureVisible.postValue(new ObservableBoolean(true)); } } else { - isInsecureVisible.setValue(new ObservableBoolean(false)); + isInsecureVisible.postValue(new ObservableBoolean(false)); } } }; @@ -271,20 +271,20 @@ public void onChanged(Spannable aUrl) { UrlUtils.isHomeUri(getApplication(), aUrl.toString()) || isLibraryVisible.getValue().get() || UrlUtils.isBlankUri(getApplication(), aUrl.toString())) { - navigationBarUrl.setValue(""); + navigationBarUrl.postValue(""); } else { - navigationBarUrl.setValue(url); + navigationBarUrl.postValue(url); } if (isBookmarksVisible.getValue().get()) { - hint.setValue(getApplication().getString(R.string.url_bookmarks_title)); + hint.postValue(getApplication().getString(R.string.url_bookmarks_title)); } else if (isHistoryVisible.getValue().get()) { - hint.setValue(getApplication().getString(R.string.url_history_title)); + hint.postValue(getApplication().getString(R.string.url_history_title)); } else { - hint.setValue(getApplication().getString(R.string.search_placeholder)); + hint.postValue(getApplication().getString(R.string.search_placeholder)); } } }; @@ -365,21 +365,21 @@ public void setUrl(@Nullable Spannable url) { // Update the URL bar only if the URL is different than the current one and // the URL bar is not focused to avoid override user input if (!getUrl().getValue().toString().equalsIgnoreCase(aURL) && !getIsFocused().getValue().get()) { - this.url.setValue(new SpannableString(aURL)); + this.url.postValue(new SpannableString(aURL)); if (index > 0) { SpannableString spannable = new SpannableString(aURL); ForegroundColorSpan color1 = new ForegroundColorSpan(mURLProtocolColor); ForegroundColorSpan color2 = new ForegroundColorSpan(mURLWebsiteColor); spannable.setSpan(color1, 0, index + 3, 0); spannable.setSpan(color2, index + 3, aURL.length(), 0); - this.url.setValue(url); + this.url.postValue(url); } else { - this.url.setValue(url); + this.url.postValue(url); } } - this.url.setValue(url); + this.url.postValue(url); } @NonNull @@ -532,7 +532,7 @@ public MutableLiveData getIsBookmarked() { } public void setIsBookmarked(boolean isBookmarked) { - this.isBookmarked.setValue(new ObservableBoolean(isBookmarked)); + this.isBookmarked.postValue(new ObservableBoolean(isBookmarked)); } @NonNull diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/NavigationURLBar.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/NavigationURLBar.java index 4e06a8a5a..228e57839 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/NavigationURLBar.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/NavigationURLBar.java @@ -240,7 +240,6 @@ public void detachFromWindow() { if (mViewModel != null) { mViewModel.getIsLoading().removeObserver(mIsLoadingObserver); mViewModel.getIsBookmarked().removeObserver(mIsBookmarkedObserver); - mViewModel.getHint().removeObserver(mHintObserver); mViewModel = null; } } @@ -255,7 +254,6 @@ public void attachToWindow(@NonNull WindowWidget aWindow) { mViewModel.getIsLoading().observe((VRBrowserActivity)getContext(), mIsLoadingObserver); mViewModel.getIsBookmarked().observe((VRBrowserActivity)getContext(), mIsBookmarkedObserver); - mViewModel.getHint().observe((VRBrowserActivity)getContext(), mHintObserver); } public void setSession(Session session) { @@ -316,8 +314,6 @@ private void handleBookmarkClick() { private Observer mIsBookmarkedObserver = aBoolean -> mBinding.bookmarkButton.clearFocus(); - private Observer mHintObserver = hint -> mBinding.urlEditText.setHint(hint); - public String getText() { return mBinding.urlEditText.getText().toString(); } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayWidget.java index 403e8afde..cc87494be 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayWidget.java @@ -93,6 +93,8 @@ private void initialize(Context aContext) { } public void updateUI() { + removeAllViews(); + LayoutInflater inflater = LayoutInflater.from(getContext()); // Inflate this data binding layout diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java index 2479ddc2d..3ef39232b 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java @@ -228,7 +228,7 @@ public void startVoiceSearch() { ActivityCompat.requestPermissions((Activity)getContext(), new String[]{Manifest.permission.RECORD_AUDIO}, VOICE_SEARCH_AUDIO_REQUEST_CODE); } else { - String locale = LocaleUtils.getVoiceSearchLocale(getContext()); + String locale = LocaleUtils.getVoiceSearchLanguageTag(getContext()); mMozillaSpeechService.setLanguage(LocaleUtils.mapToMozillaSpeechLocales(locale)); boolean storeData = SettingsStore.getInstance(getContext()).isSpeechDataCollectionEnabled(); if (SessionStore.get().getActiveSession().isPrivateMode()) { diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/ContentLanguageOptionsView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/ContentLanguageOptionsView.java index 2285f81d9..5db6c2a2b 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/ContentLanguageOptionsView.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/ContentLanguageOptionsView.java @@ -41,11 +41,9 @@ public ContentLanguageOptionsView(Context aContext, WidgetManagerDelegate aWidge private void initialize(Context aContext) { // Preferred languages adapter mPreferredAdapter = new LanguagesAdapter(getContext(), mLanguageItemCallback, true); - mPreferredAdapter.setLanguageList(LocaleUtils.getPreferredLanguages(getContext())); // Available languages adapter mAvailableAdapter = new LanguagesAdapter(getContext(), mLanguageItemCallback, false); - mAvailableAdapter.setLanguageList(LocaleUtils.getAvailableLanguages()); updateUI(); } @@ -77,12 +75,13 @@ protected void updateUI() { // Footer mBinding.footerLayout.setFooterButtonClickListener(mResetListener); + mPreferredAdapter.setLanguageList(LocaleUtils.getPreferredLanguages(getContext())); + mAvailableAdapter.setLanguageList(LocaleUtils.getAvailableLanguages(getContext())); + mBinding.executePendingBindings(); } - private OnClickListener mResetListener = (view) -> { - reset(); - }; + private OnClickListener mResetListener = (view) -> reset(); @Override public Point getDimensions() { @@ -124,29 +123,20 @@ public void onMoveDown(View view, Language language) { }; private void saveCurrentLanguages() { - SettingsStore.getInstance(getContext()).setContentLocales( - LocaleUtils.getLocalesFromLanguages(mPreferredAdapter.getItems())); - SessionStore.get().setLocales( - LocaleUtils.getLocalesFromLanguages(mPreferredAdapter.getItems())); + LocaleUtils.setPreferredLanguages(getContext(), mPreferredAdapter.getItems()); } private void refreshLanguages() { ThreadUtils.postToUiThread(() -> { mPreferredAdapter.setLanguageList(LocaleUtils.getPreferredLanguages(getContext())); - mAvailableAdapter.setLanguageList(LocaleUtils.getAvailableLanguages()); + mAvailableAdapter.setLanguageList(LocaleUtils.getAvailableLanguages(getContext())); }); } @Override protected boolean reset() { - String systemLocale = LocaleUtils.getClosestAvailableLocale(LocaleUtils.getDeviceLanguage().getId()); - List preferredLanguages = LocaleUtils.getPreferredLanguages(getContext()); - if (preferredLanguages.size() > 1 || !preferredLanguages.get(0).getId().equals(systemLocale)) { - SettingsStore.getInstance(getContext()).setContentLocales(Collections.emptyList()); - SessionStore.get().setLocales(Collections.emptyList()); - LocaleUtils.resetLanguages(); - refreshLanguages(); - } + LocaleUtils.resetPreferredLanguages(getContext()); + refreshLanguages(); return false; } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/DisplayLanguageOptionsView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/DisplayLanguageOptionsView.java index 81ab1a78f..4716cd3c5 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/DisplayLanguageOptionsView.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/DisplayLanguageOptionsView.java @@ -12,7 +12,6 @@ import androidx.databinding.DataBindingUtil; import org.mozilla.vrbrowser.R; -import org.mozilla.vrbrowser.browser.SettingsStore; import org.mozilla.vrbrowser.browser.engine.SessionStore; import org.mozilla.vrbrowser.databinding.OptionsLanguageDisplayBinding; import org.mozilla.vrbrowser.ui.views.settings.RadioGroupSetting; @@ -56,41 +55,30 @@ protected void updateUI() { // Footer mBinding.footerLayout.setFooterButtonClickListener(mResetListener); - mBinding.languageRadio.setOptions(LocaleUtils.getSupportedLocalizedLanguages(getContext())); + mBinding.languageRadio.setOptions(LocaleUtils.getSupportedLocalizedLanguages()); - String locale = LocaleUtils.getDisplayLocale(getContext()); + String languageId = LocaleUtils.getDisplayLanguageId(getContext()); mBinding.languageRadio.setOnCheckedChangeListener(mLanguageListener); - setLanguage(LocaleUtils.getIndexForSupportedLocale(locale), false); + setLanguage(LocaleUtils.getIndexForSupportedLanguageId(languageId), false); } @Override protected boolean reset() { - String systemLocale = LocaleUtils.getClosestSupportedLocale(getContext(), LocaleUtils.getDeviceLanguage().getId()); - String currentLocale = LocaleUtils.getCurrentLocale(); - if (currentLocale.equalsIgnoreCase(systemLocale)) { - setLanguage(LocaleUtils.getIndexForSupportedLocale(systemLocale), false); - SettingsStore.getInstance(getContext()).setDisplayLocale(currentLocale); - return false; - - } else { - setLanguage(LocaleUtils.getIndexForSupportedLocale(systemLocale), true); - SettingsStore.getInstance(getContext()).setDisplayLocale(null); - return true; - } + String defaultLanguageId = LocaleUtils.getDefaultLanguageId(); + setLanguage(LocaleUtils.getIndexForSupportedLanguageId(defaultLanguageId), true); + return false; } private RadioGroupSetting.OnCheckedChangeListener mLanguageListener = (radioGroup, checkedId, doApply) -> { - String currentLocale = LocaleUtils.getCurrentLocale(); - String locale = LocaleUtils.getSupportedLocaleForIndex(mBinding.languageRadio.getCheckedRadioButtonId()); + String currentLanguageId = LocaleUtils.getDisplayLanguageId(getContext()); + String languageId = LocaleUtils.getSupportedLanguageIdForIndex(mBinding.languageRadio.getCheckedRadioButtonId()); - if (!locale.equalsIgnoreCase(currentLocale)) { + if (!languageId.equalsIgnoreCase(currentLanguageId)) { setLanguage(checkedId, true); } }; - private OnClickListener mResetListener = (view) -> { - reset(); - }; + private OnClickListener mResetListener = (view) -> reset(); private void setLanguage(int checkedId, boolean doApply) { mBinding.languageRadio.setOnCheckedChangeListener(null); @@ -98,10 +86,11 @@ private void setLanguage(int checkedId, boolean doApply) { mBinding.languageRadio.setOnCheckedChangeListener(mLanguageListener); if (doApply) { - String locale = LocaleUtils.getSupportedLocaleForIndex(checkedId); - LocaleUtils.setDisplayLocale(getContext(), locale); + String languageId = LocaleUtils.getSupportedLanguageIdForIndex(checkedId); + LocaleUtils.setDisplayLanguageId(getContext(), languageId); - mWidgetManager.updateLocale(LocaleUtils.setLocale(getContext())); + Context newContext = LocaleUtils.update(getContext(), LocaleUtils.getDisplayLanguage(getContext())); + mWidgetManager.updateLocale(newContext); } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/LanguageOptionsView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/LanguageOptionsView.java index 403d2bf07..d5367960c 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/LanguageOptionsView.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/LanguageOptionsView.java @@ -98,20 +98,20 @@ protected void onDismiss() { }; private void setVoiceLanguage() { - mBinding.voiceSearchLanguageDescription.setText(getSpannedLanguageText(LocaleUtils.getVoiceSearchLanguage(getContext()).getName()), TextView.BufferType.SPANNABLE); + mBinding.voiceSearchLanguageDescription.setText(getSpannedLanguageText(LocaleUtils.getVoiceSearchLanguage(getContext()).getDisplayName()), TextView.BufferType.SPANNABLE); } private void setContentLanguage() { List preferredLanguages = LocaleUtils.getPreferredLanguages(getContext()); String text = ""; if (preferredLanguages.size() > 0) { - text = preferredLanguages.get(0).getName(); + text = preferredLanguages.get(0).getDisplayName(); } mBinding.contentLanguageDescription.setText(getSpannedLanguageText(text)); } private void setDisplayLanguage() { - mBinding.displayLanguageDescription.setText(getSpannedLanguageText(LocaleUtils.getDisplayLanguage(getContext()).getName())); + mBinding.displayLanguageDescription.setText(getSpannedLanguageText(LocaleUtils.getDisplayLanguage(getContext()).getDisplayName())); } private int getLanguageIndex(@NonNull String text) { diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/VoiceSearchLanguageOptionsView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/VoiceSearchLanguageOptionsView.java index 4e851f705..bc27e2a93 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/VoiceSearchLanguageOptionsView.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/VoiceSearchLanguageOptionsView.java @@ -12,7 +12,6 @@ import androidx.databinding.DataBindingUtil; import org.mozilla.vrbrowser.R; -import org.mozilla.vrbrowser.browser.SettingsStore; import org.mozilla.vrbrowser.browser.engine.SessionStore; import org.mozilla.vrbrowser.databinding.OptionsLanguageVoiceBinding; import org.mozilla.vrbrowser.ui.views.settings.RadioGroupSetting; @@ -56,40 +55,30 @@ protected void updateUI() { // Footer mBinding.footerLayout.setFooterButtonClickListener(mResetListener); - mBinding.languageRadio.setOptions(LocaleUtils.getSupportedLocalizedLanguages(getContext())); + mBinding.languageRadio.setOptions(LocaleUtils.getSupportedLocalizedLanguages()); - String locale = LocaleUtils.getVoiceSearchLocale(getContext()); + String languageId = LocaleUtils.getVoiceSearchLanguageId(getContext()); mBinding.languageRadio.setOnCheckedChangeListener(mLanguageListener); - setLanguage(LocaleUtils.getIndexForSupportedLocale(locale), false); + setLanguage(LocaleUtils.getIndexForSupportedLanguageId(languageId), false); } @Override protected boolean reset() { - String systemLocale = LocaleUtils.getClosestSupportedLocale(getContext(), LocaleUtils.getDeviceLanguage().getId()); - String currentLocale = LocaleUtils.getVoiceSearchLanguage(getContext()).getId(); - if (currentLocale.equalsIgnoreCase(systemLocale)) { - setLanguage(LocaleUtils.getIndexForSupportedLocale(systemLocale), false); - - } else { - setLanguage(LocaleUtils.getIndexForSupportedLocale(systemLocale), true); - SettingsStore.getInstance(getContext()).setVoiceSearchLocale(null); - } - + String defaultLanguageId = LocaleUtils.getDefaultLanguageId(); + setLanguage(LocaleUtils.getIndexForSupportedLanguageId(defaultLanguageId), true); return false; } private RadioGroupSetting.OnCheckedChangeListener mLanguageListener = (radioGroup, checkedId, doApply) -> { - String currentLocale = LocaleUtils.getVoiceSearchLocale(getContext()); - String locale = LocaleUtils.getSupportedLocaleForIndex(mBinding.languageRadio.getCheckedRadioButtonId()); + String currentLanguageId = LocaleUtils.getVoiceSearchLanguageId(getContext()); + String languageId = LocaleUtils.getSupportedLanguageIdForIndex(mBinding.languageRadio.getCheckedRadioButtonId()); - if (!locale.equalsIgnoreCase(currentLocale)) { + if (!languageId.equalsIgnoreCase(currentLanguageId)) { setLanguage(checkedId, true); } }; - private OnClickListener mResetListener = (view) -> { - reset(); - }; + private OnClickListener mResetListener = (view) -> reset(); private void setLanguage(int checkedId, boolean doApply) { mBinding.languageRadio.setOnCheckedChangeListener(null); @@ -97,8 +86,8 @@ private void setLanguage(int checkedId, boolean doApply) { mBinding.languageRadio.setOnCheckedChangeListener(mLanguageListener); if (doApply) { - String locale = LocaleUtils.getSupportedLocaleForIndex(checkedId); - LocaleUtils.setVoiceSearchLocale(getContext(), locale); + String languageId = LocaleUtils.getSupportedLanguageIdForIndex(checkedId); + LocaleUtils.setVoiceSearchLanguageId(getContext(), languageId); } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/utils/LocaleUtils.java b/app/src/common/shared/org/mozilla/vrbrowser/utils/LocaleUtils.java index cc4c12fd2..9f1049a2c 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/utils/LocaleUtils.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/utils/LocaleUtils.java @@ -6,80 +6,82 @@ import android.os.Build; import androidx.annotation.NonNull; -import androidx.annotation.StringRes; +import androidx.annotation.Nullable; import org.mozilla.vrbrowser.R; import org.mozilla.vrbrowser.browser.SettingsStore; +import org.mozilla.vrbrowser.browser.engine.SessionStore; import org.mozilla.vrbrowser.ui.adapters.Language; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.N; public class LocaleUtils { - private static Locale mSystemLocale; + private static final String DEFAULT_LANGUAGE_ID = "default"; + private static final String FALLBACK_LANGUAGE_TAG = "en-US"; + private static HashMap mLanguagesCache; + private static Map mSupportedLanguagesCache; - public static void init() { - getAllLanguages(); - } + public static Context init(@NonNull Context aContext) { + getLanguages(aContext); + getSupportedLocalizedLanguages(aContext); - public static void refresh() { - getAllLanguages(true); - } + String languageTag; + String displayId = getDisplayLanguageId(aContext); + if (displayId.equals(DEFAULT_LANGUAGE_ID)) { + languageTag = getClosestSupportedLanguageTag(getDeviceLocale().toLanguageTag()); - public static void saveSystemLocale() { - mSystemLocale = Locale.getDefault(); - } + } else { + languageTag = getDisplayLanguageTag(aContext); + } - @NonNull - public static Locale getSystemLocale() { - return mSystemLocale; - } + Language language = mLanguagesCache.get(languageTag); + if (language == null) { + language = mLanguagesCache.get(FALLBACK_LANGUAGE_TAG); + } - @NonNull - public static String getCurrentLocale() { - return Locale.getDefault().toLanguageTag(); + return setLocale(aContext, language); } - private static HashMap getAllLanguages() { - return getAllLanguages(false); - } + public static Context update(@NonNull Context aContext, @NonNull Language language) { + Context newContext = setLocale(aContext, language); + getLanguages(newContext); + getSupportedLocalizedLanguages(newContext); + setPreferredLanguages(newContext, getPreferredLanguages(newContext)); - private static HashMap getAllLanguages(boolean refresh) { - if (mLanguagesCache != null && !refresh) { - return mLanguagesCache; - } + return newContext; + } + private static HashMap getLanguages(@NonNull Context context) { Locale[] locales = Locale.getAvailableLocales(); - mLanguagesCache = new HashMap<>(); - for(Locale temp : locales) { - String languageId = temp.toLanguageTag(); - String displayName = temp.getDisplayName().substring(0, 1).toUpperCase() + temp.getDisplayName().substring(1); - Language locale = new Language(languageId, displayName + " [" + languageId + "]"); - mLanguagesCache.put(languageId, locale); - } - - Locale locale = Locale.forLanguageTag(getDeviceLocale().toLanguageTag()); - String languageId = locale.toLanguageTag(); - if (!mLanguagesCache.containsKey(languageId)) { - String displayName = locale.getDisplayName().substring(0, 1).toUpperCase() + locale.getDisplayName().substring(1); - Language language = new Language(languageId, displayName + " [" + languageId + "]"); - mLanguagesCache.put(languageId, language); - } + mLanguagesCache = new LinkedHashMap<>(); + mLanguagesCache.put(DEFAULT_LANGUAGE_ID, new Language( + getDeviceLocale(), + context.getString(R.string.settings_language_follow_device))); + + Stream.of(locales) + .sorted((o1, o2) -> o1.getDisplayName().compareTo(o2.getDisplayName())) + .forEachOrdered(item -> { + Language locale = new Language(item); + mLanguagesCache.put(item.toLanguageTag(), locale); + }); return mLanguagesCache; } - public static void resetLanguages() { + private static void resetLanguages() { mLanguagesCache.values().forEach((language) -> { if (language == getDeviceLanguage()) { language.setPreferred(true); @@ -90,44 +92,78 @@ public static void resetLanguages() { }); } - public static Language getDeviceLanguage() { + private static Language getDeviceLanguage() { return mLanguagesCache.get(getDeviceLocale().toLanguageTag()); } - public static Locale getDeviceLocale() { + private static Locale getDeviceLocale() { return Resources.getSystem().getConfiguration().getLocales().get(0); } - public static List getLocalesFromLanguages(@NonNull final List languages) { + @NonNull + private static List getLanguageTagsForLanguages(final List languages) { List result = new ArrayList<>(); - for (Language language : languages) { - result.add(language.getId()); + if (languages != null) { + for (Language language : languages) { + result.add(language.getLanguageTag()); + } } return result; } - public static List getPreferredLocales(@NonNull Context context) { - return LocaleUtils.getLocalesFromLanguages(LocaleUtils.getPreferredLanguages(context)); - } + @NonNull + private static List getIdsForLanguages(final List languages) { + if (languages != null) { + return mLanguagesCache + .entrySet() + .stream() + .filter(entry -> languages.contains(entry.getValue())) + .sorted((o1, o2) -> languages.indexOf(o1.getValue()) - languages.indexOf(o2.getValue())) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } - public static List getPreferredLanguages(@NonNull Context aContext) { - HashMap languages = getAllLanguages(); - List savedLanguages = SettingsStore.getInstance(aContext).getContentLocales(); - List preferredLanguages = new ArrayList<>(); - if (savedLanguages != null) { - for (String language : savedLanguages) { - Language lang = languages.get(getClosestAvailableLocale(language)); - if (lang != null) { - lang.setPreferred(true); - preferredLanguages.add(lang); - } - } + return new ArrayList<>(); + } + @NonNull + private static List getLanguagesForIds(final List languageIds) { + if (languageIds != null) { + return mLanguagesCache + .entrySet() + .stream() + .filter(entry -> languageIds.contains(entry.getKey())) + .sorted((o1, o2) -> languageIds.indexOf(o1.getKey()) - languageIds.indexOf(o2.getKey())) + .map(Map.Entry::getValue) + .collect(Collectors.toList()); } - if (savedLanguages == null || savedLanguages.isEmpty()) { - Language lang = languages.get(getClosestAvailableLocale(getDeviceLocale().toLanguageTag())); + return new ArrayList<>(); + } + + @NonNull + public static String getDefaultLanguageId() { + return DEFAULT_LANGUAGE_ID; + } + + // Preferred and Available language methods + + @NonNull + public static List getPreferredLanguageTags(@NonNull Context context) { + return LocaleUtils.getLanguageTagsForLanguages(LocaleUtils.getPreferredLanguages(context)); + } + + @NonNull + public static List getPreferredLanguages(@NonNull Context aContext) { + // We can't us stream here because an Android 24/25 bug the makes the stream implementation not top respect the order when iterating + // https://android.googlesource.com/platform/libcore/+/7ae7ae73754c8b82a2e396098e35553d404c69ef%5E%21/#F0 + List savedLanguageIds = SettingsStore.getInstance(aContext).getContentLocales(); + List preferredLanguages = getLanguagesForIds(savedLanguageIds); + preferredLanguages.forEach(language -> language.setPreferred(true)); + + if (savedLanguageIds == null || savedLanguageIds.isEmpty()) { + Language lang = mLanguagesCache.get(DEFAULT_LANGUAGE_ID); if (lang != null) { lang.setPreferred(true); preferredLanguages.add(lang); @@ -137,89 +173,87 @@ public static List getPreferredLanguages(@NonNull Context aContext) { return preferredLanguages; } - public static List getAvailableLanguages() { - HashMap languages = getAllLanguages(); - return languages.values().stream() - .sorted((o1, o2) -> o1.getName().compareToIgnoreCase(o2.getName())) - .collect(Collectors.toList()); + public static void setPreferredLanguages(@NonNull Context context, List languages) { + SessionStore.get().setLocales( + LocaleUtils.getLanguageTagsForLanguages(languages)); + SettingsStore.getInstance(context).setContentLocales( + LocaleUtils.getIdsForLanguages(languages)); + } + + public static void resetPreferredLanguages(@NonNull Context context) { + SettingsStore.getInstance(context).setContentLocales(Collections.emptyList()); + SessionStore.get().setLocales(Collections.emptyList()); + resetLanguages(); + } + + public static List getAvailableLanguages(@NonNull Context aContext) { + return new ArrayList<>(mLanguagesCache.values()); } + // Voice Language Methods + @NonNull - public static String getClosestAvailableLocale(@NonNull String languageTag) { - List locales = Stream.of(Locale.getAvailableLocales()).collect(Collectors.toList()); - Locale inputLocale = Locale.forLanguageTag(languageTag); - Optional outputLocale = locales.stream().filter(item -> - item.equals(inputLocale) - ).findFirst(); - - if (!outputLocale.isPresent()) { - outputLocale = locales.stream().filter(item -> - item.getLanguage().equals(inputLocale.getLanguage()) && - item.getScript().equals(inputLocale.getScript()) && - item.getCountry().equals(inputLocale.getCountry()) - ).findFirst(); - } - if (!outputLocale.isPresent()) { - outputLocale = locales.stream().filter(item -> - item.getLanguage().equals(inputLocale.getLanguage()) && - item.getCountry().equals(inputLocale.getCountry()) - ).findFirst(); - } - if (!outputLocale.isPresent()) { - outputLocale = locales.stream().filter(item -> - item.getLanguage().equals(inputLocale.getLanguage()) - ).findFirst(); + public static String getVoiceSearchLanguageId(Context context) { + String languageId = SettingsStore.getInstance(context).getVoiceSearchLocale(); + if (languageId == null) { + languageId = DEFAULT_LANGUAGE_ID; } + return languageId; + } - if (outputLocale.isPresent()) { - return outputLocale.get().toLanguageTag(); + @NonNull + public static String getVoiceSearchLanguageTag(@NonNull Context aContext) { + String languageId = getVoiceSearchLanguageId(aContext); + Language language = mSupportedLanguagesCache.get(languageId); + if (language != null) { + return language.getLanguageTag(); } - return getDeviceLocale().toLanguageTag(); + return getClosestSupportedLanguageTag(languageId); } - @NonNull - public static String getVoiceSearchLocale(@NonNull Context aContext) { - String locale = SettingsStore.getInstance(aContext).getVoiceSearchLocale(); - if (locale == null) { - locale = LocaleUtils.getDefaultSupportedLocale(aContext); - } - return locale; + public static Language getVoiceSearchLanguage(@NonNull Context aContext) { + String languageId = getVoiceSearchLanguageId(aContext); + return mSupportedLanguagesCache.get(languageId); } - public static void setVoiceSearchLocale(@NonNull Context context, @NonNull String locale) { - SettingsStore.getInstance(context).setVoiceSearchLocale(locale); + public static void setVoiceSearchLanguageId(@NonNull Context context, @NonNull String languageId) { + SettingsStore.getInstance(context).setVoiceSearchLocale(languageId); } + // Display Language Methods + @NonNull - public static Language getVoiceSearchLanguage(@NonNull Context aContext) { - return mLanguagesCache.get(getClosestAvailableLocale(getVoiceSearchLocale(aContext))); + public static String getDisplayLanguageId(@NonNull Context context) { + String languageId = SettingsStore.getInstance(context).getDisplayLocale(); + if (languageId == null) { + languageId = DEFAULT_LANGUAGE_ID; + } + return languageId; } @NonNull - public static String getDisplayLocale(Context context) { - String locale = SettingsStore.getInstance(context).getDisplayLocale(); - if (locale == null) { - locale = LocaleUtils.getDefaultSupportedLocale(context); + private static String getDisplayLanguageTag(@NonNull Context aContext) { + String languageId = getDisplayLanguageId(aContext); + Language language = mSupportedLanguagesCache.get(languageId); + if (language != null) { + return language.getLanguageTag(); } - return mapOldLocaleToNew((locale)); - } - public static void setDisplayLocale(@NonNull Context context, @NonNull String locale) { - SettingsStore.getInstance(context).setDisplayLocale(getClosestAvailableLocale(locale)); + return getClosestSupportedLanguageTag(languageId); } - @NonNull public static Language getDisplayLanguage(@NonNull Context aContext) { - return mLanguagesCache.get(getClosestAvailableLocale(getDisplayLocale(aContext))); + String languageId = getDisplayLanguageId(aContext); + return mSupportedLanguagesCache.get(languageId); } - public static Context setLocale(@NonNull Context context) { - String locale = SettingsStore.getInstance(context).getDisplayLocale(); - if (locale == null) { - locale = LocaleUtils.getDefaultSupportedLocale(context); - } - return updateResources(context, locale); + public static void setDisplayLanguageId(@NonNull Context context, @NonNull String languageId) { + SettingsStore.getInstance(context).setDisplayLocale(languageId); + } + + private static Context setLocale(@NonNull Context context, @NonNull Language language) { + return updateResources(context, language.getLanguageTag()); } private static Context updateResources(@NonNull Context context, @NonNull String language) { @@ -228,13 +262,8 @@ private static Context updateResources(@NonNull Context context, @NonNull String Resources res = context.getResources(); Configuration config = new Configuration(res.getConfiguration()); - if (Build.VERSION.SDK_INT >= JELLY_BEAN_MR1) { - config.setLocale(locale); - context = context.createConfigurationContext(config); - } else { - config.locale = locale; - res.updateConfiguration(config, res.getDisplayMetrics()); - } + config.setLocale(locale); + context = context.createConfigurationContext(config); return context; } @@ -243,16 +272,6 @@ public static Locale getLocale(@NonNull Resources res) { return (Build.VERSION.SDK_INT >= N) ? config.getLocales().get(0) : config.locale; } - public static String mapOldLocaleToNew(@NonNull String locale) { - if (locale.equalsIgnoreCase("cmn-Hant-TW")) { - locale = "zh-Hant-TW"; - } else if (locale.equalsIgnoreCase("cmn-Hans-CN")) { - locale = "zh-Hans-CN"; - } - - return locale; - } - public static String mapToMozillaSpeechLocales(@NonNull String locale) { if (locale.equalsIgnoreCase("zh-Hant-TW")) { locale = "cmn-Hant-TW"; @@ -263,105 +282,130 @@ public static String mapToMozillaSpeechLocales(@NonNull String locale) { return locale; } - public static class LocalizedLanguage { - public @StringRes int name; - public Locale locale; - - private LocalizedLanguage() {} - - public static LocalizedLanguage create(@StringRes int name, @NonNull Locale locale) { - LocalizedLanguage language = new LocalizedLanguage(); - language.name = name; - language.locale = locale; - - return language; - } - } - - private static List localizedSupportedLanguages = Stream.of( - LocalizedLanguage.create(R.string.settings_language_english, new Locale("en", "US")), - LocalizedLanguage.create(R.string.settings_language_traditional_chinese, new Locale.Builder().setLanguage("zh").setScript("Hant").setRegion("TW").build()), - LocalizedLanguage.create(R.string.settings_language_simplified_chinese, new Locale.Builder().setLanguage("zh").setScript("Hans").setRegion("CN").build()), - LocalizedLanguage.create(R.string.settings_language_japanese, new Locale("ja", "JP")), - LocalizedLanguage.create(R.string.settings_language_french, new Locale("fr", "FR")), - LocalizedLanguage.create(R.string.settings_language_german, new Locale("de", "DE")), - LocalizedLanguage.create(R.string.settings_language_spanish, new Locale("es", "ES")), - LocalizedLanguage.create(R.string.settings_language_russian, new Locale("ru", "RU")), - LocalizedLanguage.create(R.string.settings_language_korean, new Locale("ko", "KR")), - LocalizedLanguage.create(R.string.settings_language_italian, new Locale("it", "IT")), - LocalizedLanguage.create(R.string.settings_language_danish, new Locale("da", "DK")), - LocalizedLanguage.create(R.string.settings_language_polish, new Locale("pl", "PL")), - LocalizedLanguage.create(R.string.settings_language_norwegian, new Locale("nb", "NO")), - LocalizedLanguage.create(R.string.settings_language_swedish, new Locale("sv", "SE")), - LocalizedLanguage.create(R.string.settings_language_finnish, new Locale("fi", "FI")), - LocalizedLanguage.create(R.string.settings_language_dutch, new Locale("nl", "NL")) - ).collect(Collectors.toList()); - - public static String[] getSupportedLocalizedLanguages(@NonNull Context context) { - return LocaleUtils.localizedSupportedLanguages.stream().map( - item -> StringUtils.capitalize(StringUtils.getStringByLocale(context, item.name, item.locale))). - collect(Collectors.toList()).toArray(new String[]{}); - } - - public static List getSupportedLocales() { - return LocaleUtils.localizedSupportedLanguages.stream().map( - item -> item.locale.toLanguageTag()). - collect(Collectors.toList()); + private static Map getSupportedLocalizedLanguages(@NonNull Context context) { + mSupportedLanguagesCache = new LinkedHashMap() {{ + Locale locale = new Locale("en","US"); + put(locale.toLanguageTag(), new Language(locale, StringUtils.getStringByLocale(context, R.string.settings_language_english, locale))); + locale = new Locale.Builder().setLanguage("zh").setScript("Hant").setRegion("TW").build(); + put(locale.toLanguageTag(), new Language(locale, StringUtils.getStringByLocale(context, R.string.settings_language_traditional_chinese, locale))); + locale = new Locale.Builder().setLanguage("zh").setScript("Hans").setRegion("CN").build(); + put(locale.toLanguageTag(), new Language(locale, StringUtils.getStringByLocale(context, R.string.settings_language_simplified_chinese, locale))); + locale = new Locale("ja","JP"); + put(locale.toLanguageTag(), new Language(locale, StringUtils.getStringByLocale(context, R.string.settings_language_japanese, locale))); + locale = new Locale("fr","FR"); + put(locale.toLanguageTag(), new Language(locale, StringUtils.getStringByLocale(context, R.string.settings_language_french, locale))); + locale = new Locale("de","DE"); + put(locale.toLanguageTag(), new Language(locale, StringUtils.getStringByLocale(context, R.string.settings_language_german, locale))); + locale = new Locale("es","ES"); + put(locale.toLanguageTag(), new Language(locale, StringUtils.getStringByLocale(context, R.string.settings_language_spanish, locale))); + locale = new Locale("ru","RU"); + put(locale.toLanguageTag(), new Language(locale, StringUtils.getStringByLocale(context, R.string.settings_language_russian, locale))); + locale = new Locale("ko","KR"); + put(locale.toLanguageTag(), new Language(locale, StringUtils.getStringByLocale(context, R.string.settings_language_korean, locale))); + locale = new Locale("it","IT"); + put(locale.toLanguageTag(), new Language(locale, StringUtils.getStringByLocale(context, R.string.settings_language_italian, locale))); + locale = new Locale("da","DK"); + put(locale.toLanguageTag(), new Language(locale, StringUtils.getStringByLocale(context, R.string.settings_language_danish, locale))); + locale = new Locale("pl","PL"); + put(locale.toLanguageTag(), new Language(locale, StringUtils.getStringByLocale(context, R.string.settings_language_polish, locale))); + locale = new Locale("nb","NO"); + put(locale.toLanguageTag(), new Language(locale, StringUtils.getStringByLocale(context, R.string.settings_language_norwegian, locale))); + locale = new Locale("sv","SE"); + put(locale.toLanguageTag(), new Language(locale, StringUtils.getStringByLocale(context, R.string.settings_language_swedish, locale))); + locale = new Locale("fi","FI"); + put(locale.toLanguageTag(), new Language(locale, StringUtils.getStringByLocale(context, R.string.settings_language_finnish, locale))); + locale = new Locale("nl","NL"); + put(locale.toLanguageTag(), new Language(locale, StringUtils.getStringByLocale(context, R.string.settings_language_dutch, locale))); + }}; + + Locale locale = getDeviceLocale(); + String languageTag = getClosestSupportedLanguageTag(locale.toLanguageTag()); + locale = Locale.forLanguageTag(languageTag); + Language defaultLanguage = new Language(locale, context.getString(R.string.settings_language_follow_device)); + + LinkedHashMap map = new LinkedHashMap<>(); + map.put(DEFAULT_LANGUAGE_ID, defaultLanguage); + map.putAll(mSupportedLanguagesCache); + mSupportedLanguagesCache = map; + + return mSupportedLanguagesCache; } - public static int getIndexForSupportedLocale(@NonNull String locale) { - Optional locLang = localizedSupportedLanguages.stream().filter(item -> item.locale.toLanguageTag().equals(locale)).findFirst(); - return locLang.map(localizedLanguage -> localizedSupportedLanguages.indexOf(localizedLanguage)).orElse(0); - } + @NonNull + public static String[] getSupportedLocalizedLanguages() { + List result = new ArrayList<>(); + mSupportedLanguagesCache.forEach((id, language) -> { + result.add(language.getDisplayName()); + }); - public static String getSupportedLocalizedLanguageForIndex(@NonNull Context context, int index) { - return StringUtils.capitalize( - StringUtils.getStringByLocale( - context, - localizedSupportedLanguages.get(index).name, - localizedSupportedLanguages.get(index).locale)); + return result.toArray(new String[]{}); } - public static String getSupportedLocaleForIndex(int index) { - return localizedSupportedLanguages.get(index).locale.toLanguageTag(); - } + public static int getIndexForSupportedLanguageId(@NonNull String languageId) { + ArrayList keys = new ArrayList<>(mSupportedLanguagesCache.keySet()); + if (keys.contains(languageId)) { + return keys.indexOf(languageId); + } - public static String getDefaultSupportedLocale(@NonNull Context context) { - return getClosestSupportedLocale(context, getDeviceLocale().toLanguageTag()); + throw new IllegalStateException("Non existing index in the supported languages list"); } - public static String getClosestSupportedLocale(@NonNull Context context, @NonNull String languageTag) { - Locale locale = Locale.forLanguageTag(languageTag); - Optional language = LocaleUtils.localizedSupportedLanguages.stream().filter(item -> - item.locale.equals(locale) - ).findFirst(); - - if (!language.isPresent()) { - language = LocaleUtils.localizedSupportedLanguages.stream().filter(item -> - item.locale.getLanguage().equals(locale.getLanguage()) && - item.locale.getScript().equals(locale.getScript()) && - item.locale.getCountry().equals(locale.getCountry()) - ).findFirst(); - } - if (!language.isPresent()) { - language = LocaleUtils.localizedSupportedLanguages.stream().filter(item -> - item.locale.getLanguage().equals(locale.getLanguage()) && - item.locale.getCountry().equals(locale.getCountry()) - ).findFirst(); - } - if (!language.isPresent()) { - language = LocaleUtils.localizedSupportedLanguages.stream().filter(item -> - item.locale.getLanguage().equals(locale.getLanguage()) - ).findFirst(); + public static String getSupportedLanguageIdForIndex(int index) { + ArrayList keys = new ArrayList<>(mSupportedLanguagesCache.keySet()); + String key = keys.get(index); + if (key != null) { + return key; } + return DEFAULT_LANGUAGE_ID; + } + + /** + * Ideally we would do this using the [Locale.filter] method but is only available in >=26 + **/ + private static String getClosestSupportedLanguageTag(@Nullable String languageTag) { + try { + Locale locale = Locale.forLanguageTag(languageTag); + ArrayList keys = new ArrayList<>(mSupportedLanguagesCache.keySet()); + + Optional language = keys.stream().filter(item -> item.equals(locale.toLanguageTag())).findFirst(); + + if (!language.isPresent()) { + language = keys.stream().filter(item -> { + Locale itemLocale = Locale.forLanguageTag(item); + return itemLocale.getLanguage().equals(locale.getLanguage()) && + itemLocale.getScript().equals(locale.getScript()) && + itemLocale.getCountry().equals(locale.getCountry()) && + itemLocale.getVariant().equals(locale.getVariant()); + }).findFirst(); + } + if (!language.isPresent()) { + language = keys.stream().filter(item -> { + Locale itemLocale = Locale.forLanguageTag(item); + return itemLocale.getLanguage().equals(locale.getLanguage()) && + itemLocale.getScript().equals(locale.getScript()) && + itemLocale.getCountry().equals(locale.getCountry()); + }).findFirst(); + } + if (!language.isPresent()) { + language = keys.stream().filter(item -> { + Locale itemLocale = Locale.forLanguageTag(item); + return itemLocale.getLanguage().equals(locale.getLanguage()) && + itemLocale.getCountry().equals(locale.getCountry()); + }).findFirst(); + } + if (!language.isPresent()) { + language = keys.stream().filter(item -> { + Locale itemLocale = Locale.forLanguageTag(item); + return itemLocale.getLanguage().equals(locale.getLanguage()); + }).findFirst(); + } - if (language.isPresent()) { - return language.get().locale.toLanguageTag(); + return language.orElse(FALLBACK_LANGUAGE_TAG); - } else { - // If there is no closest supported locale we fallback to en-US - return "en-US"; - } + } catch (NullPointerException ignored) {} + + // If there is no closest supported locale we fallback to en-US + return FALLBACK_LANGUAGE_TAG; } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/utils/StringUtils.java b/app/src/common/shared/org/mozilla/vrbrowser/utils/StringUtils.java index 5336f7c90..9991f3316 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/utils/StringUtils.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/utils/StringUtils.java @@ -3,8 +3,8 @@ import android.annotation.TargetApi; import android.content.Context; import android.content.res.Configuration; -import android.content.res.Resources; import android.os.Build; +import android.util.Log; import androidx.annotation.NonNull; @@ -16,18 +16,13 @@ import java.util.Locale; public class StringUtils { + static final String LOGTAG = SystemUtils.createLogtag(StringUtils.class); + @NonNull @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public static String getStringByLocale(Context context, int id, Locale locale) { Configuration configuration = new Configuration(context.getResources().getConfiguration()); - // This looks like an Android bug, when trying to get the localized string for the system locale - // it returns the locale for the current context one. - Locale deviceLocale = Resources.getSystem().getConfiguration().getLocales().get(0); - if (deviceLocale.equals(locale)) { - configuration.setLocale(deviceLocale); - } else { - configuration.setLocale(locale); - } + configuration.setLocale(locale); return context.createConfigurationContext(configuration).getResources().getString(id); } @@ -120,6 +115,11 @@ public static String versionCodeToDate(final @NonNull Context context, final int @NonNull public static String capitalize(@NonNull String input) { - return input.substring(0, 1).toUpperCase() + input.substring(1); + try { + return input.substring(0, 1).toUpperCase() + input.substring(1); + } catch (StringIndexOutOfBoundsException e) { + Log.e(LOGTAG, "String index is out of bound at capitalize(). " + e); + return input; + } } } diff --git a/app/src/main/res/layout/language_item.xml b/app/src/main/res/layout/language_item.xml index fe0701871..b4450285a 100644 --- a/app/src/main/res/layout/language_item.xml +++ b/app/src/main/res/layout/language_item.xml @@ -115,7 +115,7 @@ android:gravity="center_vertical" android:paddingStart="5dp" android:singleLine="true" - android:text="@{language.name}" + android:text="@{language.displayName}" tool:text="Language Item" /> diff --git a/app/src/main/res/layout/navigation_url.xml b/app/src/main/res/layout/navigation_url.xml index ebca9141b..e54291351 100644 --- a/app/src/main/res/layout/navigation_url.xml +++ b/app/src/main/res/layout/navigation_url.xml @@ -148,7 +148,7 @@ android:focusable="true" android:focusableInTouchMode="true" android:gravity="center_vertical" - android:hint="@string/search_placeholder" + android:hint="@{viewmodel.hint}" android:imeOptions="actionGo" android:inputType="textUri" android:requiresFadingEdge="horizontal" diff --git a/app/src/main/res/values/non_L10n.xml b/app/src/main/res/values/non_L10n.xml index b175afe8a..9d2c9e6e3 100644 --- a/app/src/main/res/values/non_L10n.xml +++ b/app/src/main/res/values/non_L10n.xml @@ -56,9 +56,9 @@ https://www.mozilla.org/privacy/firefox/ https://mixedreality.mozilla.org/fxr/report?src=browser-fxr&label=browser-firefox-reality&url=%1$s https://support.mozilla.org/products/firefox-reality - https://support.mozilla.org/en-US/kb/using-voice-search-firefox-reality - https://support.mozilla.org/en-US/kb/use-firefox-another-language?as=u&utm_source=inproduct - https://support.mozilla.org/en-US/kb/choose-display-languages-multilingual-web-pages?as=u&utm_source=inproduct + https://support.mozilla.org/kb/using-voice-search-firefox-reality + https://support.mozilla.org/kb/use-firefox-another-language?as=u&utm_source=inproduct + https://support.mozilla.org/kb/choose-display-languages-multilingual-web-pages?as=u&utm_source=inproduct view position view_id diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 69e172647..fd63d4a1f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -137,6 +137,10 @@ opens a dialog box that contains language-related settings. --> Language + + Follow device language +