OSDN Git Service

Treat Latin American locales specially
authorRoozbeh Pournader <roozbeh@google.com>
Tue, 10 Jan 2017 23:24:32 +0000 (15:24 -0800)
committerRoozbeh Pournader <roozbeh@google.com>
Fri, 13 Jan 2017 01:54:01 +0000 (01:54 +0000)
Due to legacy reasons, Android translations of European Spanish were
kept under 'es', while Latin American Spanish translations were kept
under 'es-US'. The combination of this, and the new locale
preference rules in Nougat, resulted in 'es' winning over 'es-US' for
all Latin American locales, since 'es' was a direct ancestor, while
'es-US' was just a fallback.

The changes in Nougat had assumed that app developers would put Latin
American Spanish translations under 'es-419', but that could create a
backward-compatibility problem under older Android versions that did
not support three-digit region codes properly.

This CL keeps the Nougat logic and its locale parent tree, but
special-cases es-US and es-MX to be treated as equivalents of es-419
in cases where they are present and es-419 is not.

Bug: 31545805
Bug: 34126460
Test: unit tests are included
Change-Id: Iab26f41294587ee044685a5a6560520c7cbb06f7
(cherry picked from commit a192a8ced65ceea8bfe44f0eac6a394cbf80d936)

libs/androidfw/LocaleData.cpp
libs/androidfw/tests/ConfigLocale_test.cpp

index 038ef58..889d166 100644 (file)
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <array>
 #include <cstdint>
 #include <cstdlib>
 #include <cstring>
@@ -121,6 +122,16 @@ inline bool isRepresentative(uint32_t language_and_region, const char* script) {
     return (REPRESENTATIVE_LOCALES.count(packed_locale) != 0);
 }
 
+const uint32_t US_SPANISH = 0x65735553lu; // es-US
+const uint32_t MEXICAN_SPANISH = 0x65734D58lu; // es-MX
+const uint32_t LATIN_AMERICAN_SPANISH = 0x6573A424lu; // es-419
+
+// The two locales es-US and es-MX are treated as special fallbacks for es-419.
+// If there is no es-419, they are considered its equivalent.
+inline bool isSpecialSpanish(uint32_t language_and_region) {
+    return (language_and_region == US_SPANISH || language_and_region == MEXICAN_SPANISH);
+}
+
 int localeDataCompareRegions(
         const char* left_region, const char* right_region,
         const char* requested_language, const char* requested_script,
@@ -129,18 +140,30 @@ int localeDataCompareRegions(
     if (left_region[0] == right_region[0] && left_region[1] == right_region[1]) {
         return 0;
     }
-    const uint32_t left = packLocale(requested_language, left_region);
-    const uint32_t right = packLocale(requested_language, right_region);
+    uint32_t left = packLocale(requested_language, left_region);
+    uint32_t right = packLocale(requested_language, right_region);
     const uint32_t request = packLocale(requested_language, requested_region);
 
+    // If one and only one of the two locales is a special Spanish locale, we
+    // replace it with es-419. We don't do the replacement if the other locale
+    // is already es-419, or both locales are special Spanish locales (when
+    // es-US is being compared to es-MX).
+    const bool leftIsSpecialSpanish = isSpecialSpanish(left);
+    const bool rightIsSpecialSpanish = isSpecialSpanish(right);
+    if (leftIsSpecialSpanish && !rightIsSpecialSpanish && right != LATIN_AMERICAN_SPANISH) {
+        left = LATIN_AMERICAN_SPANISH;
+    } else if (rightIsSpecialSpanish && !leftIsSpecialSpanish && left != LATIN_AMERICAN_SPANISH) {
+        right = LATIN_AMERICAN_SPANISH;
+    }
+
     uint32_t request_ancestors[MAX_PARENT_DEPTH+1];
     ssize_t left_right_index;
     // Find the parents of the request, but stop as soon as we saw left or right
-    const uint32_t left_and_right[] = {left, right};
+    const std::array<uint32_t, 2> left_and_right = {{left, right}};
     const size_t ancestor_count = findAncestors(
             request_ancestors, &left_right_index,
             request, requested_script,
-            left_and_right, sizeof(left_and_right)/sizeof(left_and_right[0]));
+            left_and_right.data(), left_and_right.size());
     if (left_right_index == 0) { // We saw left earlier
         return 1;
     }
index 2bf9b12..10f4d46 100644 (file)
@@ -470,15 +470,80 @@ TEST(ConfigLocaleTest, isLocaleBetterThan_regionComparison) {
     EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
 
     fillIn("es", "AR", NULL, NULL, &request);
+    fillIn("es", "US", NULL, NULL, &config1);
+    fillIn("es", NULL, NULL, NULL, &config2);
+    // Special case for Latin American Spanish: es-MX and es-US are
+    // pseudo-parents of all Latin Ameircan Spanish locales.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("es", "MX", NULL, NULL, &request);
+    fillIn("es", "US", NULL, NULL, &config1);
+    fillIn("es", NULL, NULL, NULL, &config2);
+    // Special case for Latin American Spanish: es-MX and es-US are
+    // pseudo-parents of all Latin Ameircan Spanish locales.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("es", "AR", NULL, NULL, &request);
+    fillIn("es", "MX", NULL, NULL, &config1);
+    fillIn("es", NULL, NULL, NULL, &config2);
+    // Special case for Latin American Spanish: es-MX and es-US are
+    // pseudo-parents of all Latin Ameircan Spanish locales.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("es", "US", NULL, NULL, &request);
+    fillIn("es", "MX", NULL, NULL, &config1);
+    fillIn("es", NULL, NULL, NULL, &config2);
+    // Special case for Latin American Spanish: es-MX and es-US are
+    // pseudo-parents of all Latin Ameircan Spanish locales.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("es", "AR", NULL, NULL, &request);
+    fillIn("es", "419", NULL, NULL, &config1);
+    fillIn("es", "MX", NULL, NULL, &config2);
+    // Even though es-MX and es-US are pseudo-parents of all Latin Ameircan
+    // Spanish locales, es-419 is a closer parent.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("es", "US", NULL, NULL, &request);
+    fillIn("es", "419", NULL, NULL, &config1);
+    fillIn("es", "MX", NULL, NULL, &config2);
+    // Even though es-MX and es-US are pseudo-parents of all Latin Ameircan
+    // Spanish locales, es-419 is a closer parent.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("es", "MX", NULL, NULL, &request);
+    fillIn("es", "419", NULL, NULL, &config1);
+    fillIn("es", "US", NULL, NULL, &config2);
+    // Even though es-MX and es-US are pseudo-parents of all Latin Ameircan
+    // Spanish locales, es-419 is a closer parent.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("es", "AR", NULL, NULL, &request);
     fillIn("es", "MX", NULL, NULL, &config1);
     fillIn("es", "BO", NULL, NULL, &config2);
-    // A representative locale is better if they are equidistant.
+    // Special case for Latin American Spanish: es-MX and es-US are
+    // pseudo-parents of all Latin Ameircan Spanish locales.
     EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
     EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
 
     fillIn("es", "AR", NULL, NULL, &request);
     fillIn("es", "US", NULL, NULL, &config1);
     fillIn("es", "BO", NULL, NULL, &config2);
+    // Special case for Latin American Spanish: es-MX and es-US are
+    // pseudo-parents of all Latin Ameircan Spanish locales.
+    EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+    EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+    fillIn("es", "IC", NULL, NULL, &request);
+    fillIn("es", "ES", NULL, NULL, &config1);
+    fillIn("es", "GQ", NULL, NULL, &config2);
     // A representative locale is better if they are equidistant.
     EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
     EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));