-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix custom app language selection issues across various android versions #7413
Conversation
* Retain custom language selection across application kill/restarts on devices running api 24 and above. * Instantly apply the language selection and update all views on devices running api 23 and less. * Change to the app language now consistent across all versions of android. * Update the title of MediaBrowserActivity to use the localized string. Tickets: #7267, #7265
We have been discussing this issue on Slack and I'll just list the alternative solutions we can go about it in the order of my own preference:
I am going to defer this issue to @maxme since he probably knows about this issue more than any of us. /cc @mzorz |
While reviewing the code changes in this PR with @oguzkocer, it was decided that using The How
For Android 25 and above, this no longer works since the locale is updated differently. Looking at the code comment, the developer was worried that Locale.getDefault() would return an outdated locale, but this was only because the default locale was not being updated when the app language was changed (
Technically we no longer need the
Until it is changed, however, the tag options drop down on the Notification view (and likely other places) will not reflect the app language change until the app is restarted for devices running API 25 and above. Other Changes Included@oguzkocer requested that I remove
Ok, maybe I am being a tad bit dramatic. In any event, I committed all those changes separately from the main fix just in case someone decides they liked it better the other way 😉 Now for some noteworthy funkinesscode word: Language Toggling Emulator Test Results by API
cc: @maxme @oguzkocer |
By @oguzkocer :
Regardless on which route we'll take on this particular PR, I would like us to explore that ^ idea. I'd love having the linter help us do the right thing while coding and implementing conventions this way sounds awesome! |
My preference here is to go with option 1. I suggest to check usage in Tracks (if this is possibile) and if we don't want to kill the feature because it's still used by some of our users, we can just show a dialog that explains to restart the app to apply the changes (if this makes sense). Or limit the feature and patches to latest Android versions. Also mark this feature deprecated and remove it in one year or so. |
@AmandaRiu I think there was a slight miscommunication because what I was trying to say was I personally prefer not to add a base activity, however I am not completely against the idea. As we also discussed on Slack yesterday, I am actually OK with it if all we use it for will be the language issue. I am just worried that once that base activity is there we'll start adding more stuff which will complicate our lives in the long run. |
@oguzkocer Yeah I know and I half agree with you which is why I implemented it 😸 I figured I'd put it in and leave it up to @maxme |
I agree that having a I agree that having the same method repeated in all class descending Activity is not a good idea either and very prone to error (unless we had a lint rule, but that kills me a little to add this kind of rules). I disagree on the removal of the language selector. It's the only way to switch the app to certain languages (because some languages are not supported by in the core settings of Android). We'll also have to drop the work of our translators (we have very active translators for languages that are used by very few users). This would send a bad signal to our users and translators, and this only for avoiding a minor issue. I think we can live with #7265 (and we could comment on it and mark it with the |
BTW, I misread the whole convo
Yes that's an option too, for API levels < 21. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AmandaRiu With my limited knowledge on the subject, it pains me to say that these changes look good :) I've left a few minor comments and thoughts. Let me know what you think about them and we can merge this in.
I don't know if we are planning to add a lint check for these beautiful attachBaseContext
methods, but we might want to open a new issue for that and ping Maxime and Alex in it to start a discussion.
// In case of a "ArithmeticException: divide by zero", force the use of "US" locale to format the string. | ||
txtFollowCount.setText(String.format(Locale.US, getContext().getString(R.string.reader_label_follow_count), | ||
blogInfo.numSubscribers)); | ||
txtFollowCount.setText(String.format( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like our try and catch are the same now. Maybe we don't need the try and catch anymore, but if we do, shouldn't they be different statements?
} catch (ArithmeticException exception) { | ||
// See https://github.com/wordpress-mobile/WordPress-Android/issues/5568 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we do need this try/catch I assume we still need to deal with this bug in which case I think we should keep the comment here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh yeah good catch. The bug is still relevant, but now it's being properly handled by my getSafeLocale(...)
method so I can remove this catch altogether.
* @param language The language to compare | ||
* @return True if the languages are the same, else false | ||
*/ | ||
public static boolean isDifferentLanguage(String language) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might just be me, but I think it'd be better if we had a isSameLanguage
method instead of isDifferentLanguage
to avoid the double negative like the one in AppSettingsFragment
:
if (!LocaleManager.isDifferentLanguage(languageCode)) {
return;
}
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love it...I'll get it updated 💯
return; | ||
} | ||
|
||
if (Locale.getDefault().toString().equals(newLocale.toString())) { | ||
// remove custom locale key when original device locale is selected | ||
mSettings.edit().remove(LANGUAGE_PREF_KEY).apply(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this mSettings
property anymore after these changes. Should we remove that as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Removed
public static Context setNewLocale(Context context, String language) { | ||
Locale newLocale = WPPrefUtils.languageLocale(language); | ||
|
||
if (Locale.getDefault().toString().equals(newLocale.toString())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can use the helper method isDifferentLanguage
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed :)
* @return True if the languages are the same, else false | ||
*/ | ||
public static boolean isDifferentLanguage(String language) { | ||
Locale newLocale = WPPrefUtils.languageLocale(language); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this shouldn't really be a problem (at least not right now), but I am wondering if it's worth to check the emptiness of language
ourselves instead of letting WPPrefUtils.languageLocale()
to return the default language to us. Alternatively, it might be even better to just mark the language
as @NonNull
so the activity/fragment that's calling this method has to deal with the issue.
To clarify, let's say the current locale is Turkish
and we somehow passed an empty string to this method, it'll return true since Turkish
is not the default locale. By handling this case in the method we can return false
and that'd be good enough. But if we were to change the method to isSameLocale
, returning false
in that case is not what we want. So, I feel the @NonNull
approach is the better one, assuming that it's even worth to do anything here since it will pretty much only prevent developer errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default locale is the current locale unless the user is in the process of manually changing the language in which case the language string would not be empty and if it were then we wouldn't want to make a change so we would want it to return the default locale. When a new language is requested a locale object is created from that language and then set as the default locale. When the app is closed and restarts, one of the first few things it does during application startup is to check for the presence of a language override, and if it exists, the default locale is set from it immediately.
Also the @NonNull
would only check for a null object and not an empty string so it wouldn't gain us anything. 🤔
Oh, but I did move all the language-related methods out of WPPrefUtils since they were all just helper methods for localization and not really relevant to preferences. It's also nice to keep all those methods in one place so developers can find them in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default locale is the current locale unless the user is in the process of manually changing the language in which case the language string would not be empty
That sounds good to me, it was just a thought, because some check outside the control of isDifferentLanguage
was happening in the WPPrefUtils.languageLocale
which is always a worrisome thing for me. In this specific case, there is no harm and we don't have to do anything here. Thanks for indulging my question.
Also the @nonnull would only check for a null object and not an empty string so it wouldn't gain us anything.
It doesn't fully fix it, but it covers half the case. Having said that, I was not really sure about if it's good enough either. I think the move to the LocaleManager
will help with my concern 👍
* selected language is properly saved and resources appropriately updated for the | ||
* android version. | ||
*/ | ||
public abstract class LocaleManager { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot to mention this during my review. I am curious about why this class is marked as abstract
, is there a benefit that I am missing about it? All abstract
tells me is that we might have a different implementation, something like WordPressLocaleManager
but we want to be able to pass the LocaleManager
around instead which I don't think is the case here. I'd love to learn if there are other benefits I didn't know about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It prevents people from instantiating an instance of this class since it's a utility class. I can remove it if you think it's misleading...I think it's one of those things where some developers do it and some do not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if we create an instance, we won't be able to do anything with it, but that's a good point. Just for consistency, I think it might still be better not to make it abstract
as other such classes don't follow that pattern.
P.S: I actually don't know which one I'd prefer if I was working solo in a project 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, will do :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
…caleManager * Move all language related helper methods from WPPrefUtils to LocaleManager * Remove unnecessary try/catch
…eLanguageCode(context) and deprecate method signatures Methods stopped working as expected as of API 25. Context argument is no longer needed, but may still be used by other libraries so deprecated those method signatures and added new updated method signatures.
@oguzkocer Implemented suggestions. I also went in and made the suggested changes to WordPressUtils.languageUtils() as explained here: WordPressUtils -> LanguageUtils.getCurrentDeviceLanguage(...) is fetching the locale using this logic:
For Android 25 and above, this no longer works since the locale is updated differently. Looking at the code comment, the developer was worried that Locale.getDefault() would return an outdated locale, but this was only because the default locale was not being updated when the app language was changed (Locale.setDefault(locale)). This has been added so Locale.getDefault() will always return the current locale settings no matter the version of Android. So this method should be changed to:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming I didn't miss something, I think we are almost good to go. I've left a couple super minor comments.
/** | ||
* Generates display strings for given language codes. Used as entries in language preference. | ||
*/ | ||
@android.support.annotation.Nullable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's worth importing this even if just for consistency: import android.support.annotation.Nullable;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✔️
} | ||
|
||
@SuppressWarnings("WeakerAccess") | ||
public static Locale getCurrentDeviceLanguage() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can mark this as private
and remove the suppression.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I guess we don't know if this class is used at all outside of WPAndroid. Let's skip this change for now.
@@ -1,24 +1,43 @@ | |||
package org.wordpress.android.util; | |||
|
|||
import android.content.Context; | |||
import android.support.annotation.Nullable; | |||
|
|||
import java.util.Locale; | |||
|
|||
/** | |||
* Methods for dealing with i18n messages | |||
*/ | |||
public class LanguageUtils { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was trying to understand the deprecated methods in this class, but I feel like we should drop this all together if we are not going to use it. What do you think?
P.S: I don't think it has to happen in this PR, I'd actually prefer a separate one for this.
public static Context setNewLocale(Context context, String language) { | ||
Locale newLocale = languageLocale(language); | ||
|
||
if (Locale.getDefault().toString().equals(newLocale.toString())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this change was missed #7413 (comment)
…pdated login in method to just exit it the new language is the same as the current language.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great, thanks a lot for this PR @AmandaRiu
3117e2d9a3 Bump version to 1.20.1 6d127814c4 Update Travis build tools in all subtrees to v27 6e697f276e Update Gradle wrapper in all subtrees to 4.4 22d3f56300 Fix a build problem 3467a1f7d7 Update Bintray plugin to 0.8.1, which supports Gradle 4.4 f8a61eed7c Update Android Gradle plugin to 3.1 dacea1bc1b Update WordPress Utils support lib modules to 27.1.0 dc6cc663c0 Merge branch 'develop' into feature/awesome-accessibility 5aefdeafeb using NumberFormat.getInstance() factory method as suggested in docs https://developer.android.com/reference/java/text/DecimalFormat.html 3111f9361d simplified statement fc23f04a64 put DecimalFormat instance back, as we need to re-create it once the local/settings have changed 9418672d5e fixed small typo b13bbd1991 made constant final d3fda0f3eb added file size units and made them translatable f650818eb9 fixed minor typo in comment ddb0419e4f using DECIMAL_INSTANCE instead of new Decimal() b86cc2478a Fix style warnings. 917befde81 Add method to format percentages. 4d63e29192 Merge branch 'develop' into issue/display_site_quota 3d9b3c80ea Display site settings quota. f0eb0b8f18 Merge pull request #7413 from wordpress-mobile/issue/language-default aac0ac3a60 Fix broken methods getCurrentDeviceLanguage(context), getCurrentDeviceLanguageCode(context) and deprecate method signatures abfaa0ed59 Merge branch 'develop' into feature/santa-lint-issues 85774cc246 Replace strings in code for better support of RTL translations 88b9f6332c Only set up checkstyle for utils if it's not already defined 493063e7a2 Merge branch 'develop' of github.com:wordpress-mobile/WordPress-Android into feature/awesome-accesibility-merge-dev-fixes 4c6e99dc65 Fix visibility modifier in AccessibilityUtils 884e5f6626 Increase toast with action duration when accessibility mode is enabled git-subtree-dir: libs/utils git-subtree-split: 3117e2d9a39ff10ee01fb9b1177a37990220585e
Fixes #7267, #7265
Updating the language of the App in real time is handled differently across different versions of Android. This PR centralizes language updates to a few new classes:
Any Activity that should be sensitive to Locale changes should extend either
BaseActivity
orBaseListActivity
.LocaleManager
contains a set of helper methods/utilities for properly persisting, fetching and applying locale settings appropriate to the active android version.AppSettingsFragment
now callsSystem.exit(0)
to force an instance restart of the app so the language of the app is updated instantly, and to give a consistent experience across all versions of Android.Once localization was updated to be carried with the active context, I ran into an issue where devices running API 24 & 25 would suddenly crash with a fatal "division by zero" exception while attempting to parse and format a string containing locale-specific grouping separators. For example, the following string:
%,d people like this
would cause a fatal crash while attempting to format it usingString.format(baseString, val)
. Turns out this is a known Java bug for Android 7.x.The only way around this bug is to pass in the locale to String.format(), BUT, Locale.getDefault() will not work when the
language
property of the Locale object contains any regional information. Specifically, it will only work iflanguage=en
and will not work iflanguage=en_US
. CreatedLocaleManager.getSafeLocale(context)
to obtain a properly formatted locale object for the workaround and updated any places where this may be a problem by searching for%,d
instrings.xml
.Fix tested on emulators running the following versions: 17, 19, 22, 23, 24, 25, 26, 27
Screenshots
API 19
API 26
To test:
- Labels are updated in the filter bar of the notification tab.
- Tag options dropdown are updated in the Reader.