diff --git a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java index 078bd7ce8..d3f0b58ce 100644 --- a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java +++ b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java @@ -194,7 +194,7 @@ public class FDroidApp extends Application { @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - Languages.setLanguage(this, Preferences.get().getLangauge(), false); + Languages.setLanguage(this, Preferences.get().getLanguage(), false); } @Override @@ -212,7 +212,7 @@ public class FDroidApp extends Application { } Preferences.setup(this); Languages.setup(getClass(), R.string.pref_language_default); - Languages.setLanguage(this, Preferences.get().getLangauge(), false); + Languages.setLanguage(this, Preferences.get().getLanguage(), false); ACRA.init(this); if (isAcraProcess()) { diff --git a/app/src/main/java/org/fdroid/fdroid/Languages.java b/app/src/main/java/org/fdroid/fdroid/Languages.java index c30512c32..68d75ff83 100644 --- a/app/src/main/java/org/fdroid/fdroid/Languages.java +++ b/app/src/main/java/org/fdroid/fdroid/Languages.java @@ -117,9 +117,15 @@ public final class Languages { @TargetApi(17) public static void setLanguage(final ContextWrapper contextWrapper, String language, boolean refresh) { + if (Build.VERSION.SDK_INT >= 24) { + Utils.debugLog(TAG, "Languages.setLanguage() ignored on >= android-24"); + Preferences.get().clearLanguage(); + return; + } if (locale != null && TextUtils.equals(locale.getLanguage(), language) && (!refresh)) { return; // already configured } else if (language == null || language.equals(USE_SYSTEM_DEFAULT)) { + Preferences.get().clearLanguage(); locale = DEFAULT_LOCALE; } else { /* handle locales with the country in it, i.e. zh_CN, zh_TW, etc */ @@ -147,6 +153,10 @@ public final class Languages { * @param activity the {@code Activity} to force reload */ public static void forceChangeLanguage(Activity activity) { + if (Build.VERSION.SDK_INT >= 24) { + Utils.debugLog(TAG, "Languages.forceChangeLanguage() ignored on >= android-24"); + return; + } Intent intent = activity.getIntent(); if (intent == null) { // when launched as LAUNCHER return; @@ -158,18 +168,6 @@ public final class Languages { activity.overridePendingTransition(0, 0); } - /** - * @return the name of the language based on the locale. - */ - public String getName(String locale) { - String ret = nameMap.get(locale); - // if no match, try to return a more general name (i.e. English for en_IN) - if (ret == null && locale.contains("_")) { - ret = nameMap.get(locale.split("_")[0]); - } - return ret; - } - /** * @return an array of the names of all the supported languages, sorted to * match what is returned by {@link Languages#getSupportedLocales()}. @@ -178,19 +176,6 @@ public final class Languages { return nameMap.values().toArray(new String[nameMap.size()]); } - public int getPosition(Locale locale) { - String localeName = locale.getLanguage(); - int i = 0; - for (String key : nameMap.keySet()) { - if (TextUtils.equals(key, localeName)) { - return i; - } else { - i++; - } - } - return -1; - } - /** * @return sorted list of supported locales. */ diff --git a/app/src/main/java/org/fdroid/fdroid/Preferences.java b/app/src/main/java/org/fdroid/fdroid/Preferences.java index 92977927e..8609870a5 100644 --- a/app/src/main/java/org/fdroid/fdroid/Preferences.java +++ b/app/src/main/java/org/fdroid/fdroid/Preferences.java @@ -229,8 +229,12 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh .replaceAll(" ", "-"); } - public String getLangauge() { - return preferences.getString(Preferences.PREF_LANGUAGE, ""); + public String getLanguage() { + return preferences.getString(Preferences.PREF_LANGUAGE, Languages.USE_SYSTEM_DEFAULT); + } + + public void clearLanguage() { + preferences.edit().remove(Preferences.PREF_LANGUAGE).apply(); } public String getLocalRepoName() { diff --git a/app/src/main/java/org/fdroid/fdroid/data/App.java b/app/src/main/java/org/fdroid/fdroid/data/App.java index e1d46ea26..f7d04b79a 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/App.java +++ b/app/src/main/java/org/fdroid/fdroid/data/App.java @@ -7,9 +7,12 @@ import android.content.pm.FeatureInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; +import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.database.Cursor; +import android.os.Build; import android.os.Environment; +import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.Nullable; @@ -359,64 +362,116 @@ public class App extends ValueObject implements Comparable, Parcelable { /** * Parses the {@code localized} block in the incoming index metadata, * choosing the best match in terms of locale/language while filling as - * many fields as possible. The first English locale found is loaded, then - * {@code en-US} is loaded over that, since that's the most common English - * for software. Then the first language match, and then finally the - * current locale for this device, given it precedence over all the others. + * many fields as possible. It first sets up a locale list based on user + * preference and the locales available for this app, then picks the texts + * based on that list. One thing that makes this tricky is that any given + * locale block in the index might not have all the fields. So when filling + * out each value, it needs to go through the whole preference list each time, + * rather than just taking the whole block for a specific locale. This is to + * ensure that there is something to show, as often as possible. *

- * It is still possible that the fields will be loaded directly without any - * locale info. This comes from the old-style {@code .txt} app metadata + * It is still possible that the fields will be loaded directly by Jackson + * without any locale info. This comes from the old-style, inline app metadata * fields that do not have locale info. They should not be used if the - * {@code Localized} block is specified. + * {@code localized} block is included in the index. Also, null strings in + * the {@code localized} block should not overwrite Name/Summary/Description + * strings with empty/null if they were set directly by Jackson. + *

+ * Choosing the locale to use follows two sets of rules, one for Android versions + * older than {@code android-24} and the other for {@code android-24} or newer. + * The system-wide language preference list was added in {@code android-24}. + *

+ * On {@code >= android-24}, it is by design that this does not fallback to other + * country-specific locales, e.g. {@code fr-CH} does not fall back on {@code fr-FR}. + * If someone wants to fallback to {@code fr-FR}, they can add it to the system + * language list. There are many cases where it is inappropriate to fallback to a + * different country-specific locale, for example {@code de-DE --> de-CH} or + * {@code zh-CN --> zh-TW}. + *

+ * On {@code < android-24}, the user can only set a single + * locale with a country as an option, so here it makes sense to try to fallback + * on other country-specific locales, rather than English. */ @JsonProperty("localized") private void setLocalized(Map> localized) { // NOPMD Locale defaultLocale = Locale.getDefault(); String languageTag = defaultLocale.getLanguage(); - String localeTag = languageTag + "-" + defaultLocale.getCountry(); - Set locales = localized.keySet(); - Set localesToUse = new LinkedHashSet<>(); - - if (locales.contains(localeTag)) { - localesToUse.add(localeTag); + String countryTag = defaultLocale.getCountry(); + String localeTag; + if (TextUtils.isEmpty(countryTag)) { + localeTag = languageTag; + } else { + localeTag = languageTag + "-" + countryTag; } - for (String l : locales) { - if (l.startsWith(languageTag)) { - localesToUse.add(l); - break; + + Set availableLocales = localized.keySet(); + Set localesToUse = new LinkedHashSet<>(); + if (Build.VERSION.SDK_INT >= 24) { + LocaleList localeList = Resources.getSystem().getConfiguration().getLocales(); + for (String toUse : localeList.toLanguageTags().split(",")) { + localesToUse.add(toUse); + for (String l : availableLocales) { + if (l.equals(toUse.split("-")[0])) { + localesToUse.add(l); + break; + } + } + } + } else { + if (availableLocales.contains(localeTag)) { + localesToUse.add(localeTag); + } + if (availableLocales.contains(languageTag)) { + localesToUse.add(languageTag); + } + for (String l : availableLocales) { + if (l.startsWith(languageTag)) { + localesToUse.add(l); + } } } - if (locales.contains("en-US")) { + if (availableLocales.contains("en-US")) { localesToUse.add("en-US"); } - for (String l : locales) { + for (String l : availableLocales) { if (l.startsWith("en")) { localesToUse.add(l); break; } } - // if key starts with Upper case, its set by humans - String value = getLocalizedEntry(localized, localesToUse, "Video"); + + whatsNew = getLocalizedEntry(localized, localesToUse, "whatsNew"); + String value = getLocalizedEntry(localized, localesToUse, "video"); if (!TextUtils.isEmpty(value)) { video = value.split("\n", 1)[0]; } - whatsNew = getLocalizedEntry(localized, localesToUse, "WhatsNew"); - // Name, Summary, Description existed before localization so they shouldn't replace - // non-localized old data format with a null or blank string - value = getLocalizedEntry(localized, localesToUse, "Name"); + value = getLocalizedEntry(localized, localesToUse, "name"); if (!TextUtils.isEmpty(value)) { name = value; } - value = getLocalizedEntry(localized, localesToUse, "Summary"); + value = getLocalizedEntry(localized, localesToUse, "summary"); if (!TextUtils.isEmpty(value)) { summary = value; } - value = getLocalizedEntry(localized, localesToUse, "Description"); + value = getLocalizedEntry(localized, localesToUse, "description"); if (!TextUtils.isEmpty(value)) { description = value; } - // if key starts with lower case, its generated based on finding the files featureGraphic = getLocalizedGraphicsEntry(localized, localesToUse, "featureGraphic"); promoGraphic = getLocalizedGraphicsEntry(localized, localesToUse, "promoGraphic"); tvBanner = getLocalizedGraphicsEntry(localized, localesToUse, "tvBanner"); diff --git a/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java b/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java index a4e6cdd3a..f15843f50 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java +++ b/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java @@ -65,10 +65,15 @@ public class PreferencesFragment extends PreferenceFragment updatePrivilegedExtensionPref = findPreference(Preferences.PREF_UNINSTALL_PRIVILEGED_APP); AppCompatListPreference languagePref = (AppCompatListPreference) findPreference(Preferences.PREF_LANGUAGE); - Languages languages = Languages.get(getActivity()); - languagePref.setDefaultValue(Languages.USE_SYSTEM_DEFAULT); - languagePref.setEntries(languages.getAllNames()); - languagePref.setEntryValues(languages.getSupportedLocales()); + if (Build.VERSION.SDK_INT >= 24) { + PreferenceCategory category = (PreferenceCategory) findPreference("pref_category_display"); + category.removePreference(languagePref); + } else { + Languages languages = Languages.get(getActivity()); + languagePref.setDefaultValue(Languages.USE_SYSTEM_DEFAULT); + languagePref.setEntries(languages.getAllNames()); + languagePref.setEntryValues(languages.getSupportedLocales()); + } } private void checkSummary(String key, int resId) { @@ -78,7 +83,9 @@ public class PreferencesFragment extends PreferenceFragment private void entrySummary(String key) { ListPreference pref = (ListPreference) findPreference(key); - pref.setSummary(pref.getEntry()); + if (pref != null) { + pref.setSummary(pref.getEntry()); + } } private void textSummary(String key, int resId) { @@ -150,7 +157,7 @@ public class PreferencesFragment extends PreferenceFragment entrySummary(key); if (changing) { Activity activity = getActivity(); - Languages.setLanguage(activity, Preferences.get().getLangauge(), false); + Languages.setLanguage(activity, Preferences.get().getLanguage(), false); Languages.forceChangeLanguage(activity); } break; diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 8f560d234..9ebaaf4c7 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -1,13 +1,11 @@ - - - +