From 0a59c5c6e59a5148ba8a9c647ed7f7b12bddab41 Mon Sep 17 00:00:00 2001
From: Hans-Christoph Steiner <hans@eds.org>
Date: Fri, 28 Apr 2017 10:17:20 +0200
Subject: [PATCH] fully write up locale choosing for the 'localized' block

This is how locales are handled when parsing the index from the server.
---
 .../main/java/org/fdroid/fdroid/data/App.java | 55 +++++++++++++++----
 1 file changed, 43 insertions(+), 12 deletions(-)

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 e8a61688e..f7d04b79a 100644
--- a/app/src/main/java/org/fdroid/fdroid/data/App.java
+++ b/app/src/main/java/org/fdroid/fdroid/data/App.java
@@ -362,15 +362,49 @@ public class App extends ValueObject implements Comparable<App>, 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.
      * <p>
-     * 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.
+     * <p>
+     * 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}.
+     * <ul>
+     * <li>{@code >= android-24}<ol>
+     * <li>the country variant {@code de-AT} from the user locale list
+     * <li>only the language {@code de} from the above locale
+     * <li>{@code en-US} since its the most common English for software
+     * <li>the first available {@code en} locale
+     * </ol></li>
+     * <li>{@code < android-24}<ol>
+     * <li>the country variant from the user locale: {@code de-AT}
+     * <li>only the language from the above locale:  {@code de}
+     * <li>all available locales with the same language:  {@code de-BE}
+     * <li>{@code en-US} since its the most common English for software
+     * <li>all available {@code en} locales
+     * </ol></li>
+     * </ul>
+     * 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}.
+     * <p>
+     * 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<String, Map<String, Object>> localized) { // NOPMD
@@ -419,14 +453,12 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
                 break;
             }
         }
-        // if key starts with Upper case, its set by humans
+
+        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");
         if (!TextUtils.isEmpty(value)) {
             name = value;
@@ -440,7 +472,6 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
             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");