Print this page
rev 5615 : 6336885: RFE: Locale Data Deployment Enhancements
4609153: Provide locale data for Indic locales
5104387: Support for gl_ES locale (galician language)
6337471: desktop/system locale preferences support
7056139: (cal) SPI support for locale-dependent Calendar parameters
7058206: Provide CalendarData SPI for week params and display field value names
7073852: Support multiple scripts for digits and decimal symbols per locale
7079560: [Fmt-Da] Context dependent month names support in SimpleDateFormat
7171324: getAvailableLocales() of locale sensitive services should return the actual availability of locales
7151414: (cal) Support calendar type identification
7168528: LocaleServiceProvider needs to be aware of Locale extensions
7171372: (cal) locale's default Calendar should be created if unknown calendar is specified
Summary: JEP 127: Improve Locale Data Packaging and Adopt Unicode CLDR Data (part 1 w/o Jigsaw. by Naoto Sato and Masayoshi Okutsu)

Split Close
Expand all
Collapse all
          --- old/src/share/classes/java/util/Locale.java
          +++ new/src/share/classes/java/util/Locale.java
   1    1  /*
   2      - * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved.
        2 + * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved.
   3    3   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4    4   *
   5    5   * This code is free software; you can redistribute it and/or modify it
   6    6   * under the terms of the GNU General Public License version 2 only, as
   7    7   * published by the Free Software Foundation.  Oracle designates this
   8    8   * particular file as subject to the "Classpath" exception as provided
   9    9   * by Oracle in the LICENSE file that accompanied this code.
  10   10   *
  11   11   * This code is distributed in the hope that it will be useful, but WITHOUT
  12   12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
↓ open down ↓ 30 lines elided ↑ open up ↑
  43   43  import java.io.IOException;
  44   44  import java.io.ObjectInputStream;
  45   45  import java.io.ObjectOutputStream;
  46   46  import java.io.ObjectStreamField;
  47   47  import java.io.Serializable;
  48   48  import java.security.AccessController;
  49   49  import java.text.MessageFormat;
  50   50  import java.util.spi.LocaleNameProvider;
  51   51  
  52   52  import sun.security.action.GetPropertyAction;
  53      -import sun.util.LocaleServiceProviderPool;
       53 +import sun.util.locale.provider.LocaleServiceProviderPool;
  54   54  import sun.util.locale.BaseLocale;
  55   55  import sun.util.locale.InternalLocaleBuilder;
  56   56  import sun.util.locale.LanguageTag;
  57   57  import sun.util.locale.LocaleExtensions;
  58   58  import sun.util.locale.LocaleObjectCache;
  59   59  import sun.util.locale.LocaleSyntaxException;
  60   60  import sun.util.locale.LocaleUtils;
  61   61  import sun.util.locale.ParseStatus;
  62      -import sun.util.locale.UnicodeLocaleExtension;
  63      -import sun.util.resources.LocaleData;
       62 +import sun.util.locale.provider.LocaleProviderAdapter;
  64   63  import sun.util.resources.OpenListResourceBundle;
  65   64  
  66   65  /**
  67   66   * A <code>Locale</code> object represents a specific geographical, political,
  68   67   * or cultural region. An operation that requires a <code>Locale</code> to perform
  69   68   * its task is called <em>locale-sensitive</em> and uses the <code>Locale</code>
  70   69   * to tailor information for the user. For example, displaying a number
  71   70   * is a locale-sensitive operation&mdash; the number should be formatted
  72   71   * according to the customs and conventions of the user's native country,
  73   72   * region, or culture.
↓ open down ↓ 884 lines elided ↑ open up ↑
 958  957       */
 959  958      public static String[] getISOLanguages() {
 960  959          if (isoLanguages == null) {
 961  960              isoLanguages = getISO2Table(LocaleISOData.isoLanguageTable);
 962  961          }
 963  962          String[] result = new String[isoLanguages.length];
 964  963          System.arraycopy(isoLanguages, 0, result, 0, isoLanguages.length);
 965  964          return result;
 966  965      }
 967  966  
 968      -    private static final String[] getISO2Table(String table) {
      967 +    private static String[] getISO2Table(String table) {
 969  968          int len = table.length() / 5;
 970  969          String[] isoTable = new String[len];
 971  970          for (int i = 0, j = 0; i < len; i++, j += 5) {
 972  971              isoTable[i] = table.substring(j, j + 2);
 973  972          }
 974  973          return isoTable;
 975  974      }
 976  975  
 977  976      /**
 978  977       * Returns the language code of this Locale.
↓ open down ↓ 48 lines elided ↑ open up ↑
1027 1026       * Returns the variant code for this locale.
1028 1027       *
1029 1028       * @return The variant code, or the empty string if none is defined.
1030 1029       * @see #getDisplayVariant
1031 1030       */
1032 1031      public String getVariant() {
1033 1032          return baseLocale.getVariant();
1034 1033      }
1035 1034  
1036 1035      /**
     1036 +     * Returns {@code true} if this {@code Locale} has any <a href="#def_extensions">
     1037 +     * extensions</a>.
     1038 +     *
     1039 +     * @return {@code true} if this {@code Locale} has any extensions
     1040 +     * @since 1.8
     1041 +     */
     1042 +    public boolean hasExtensions() {
     1043 +        return localeExtensions != null;
     1044 +    }
     1045 +
     1046 +    /**
     1047 +     * Returns a copy of this {@code Locale} with no <a href="#def_extensions">
     1048 +     * extensions</a>. If this {@code Locale} has no extensions, this {@code Locale}
     1049 +     * is returned.
     1050 +     *
     1051 +     * @return a copy of this {@code Locale} with no extensions, or {@code this}
     1052 +     *         if {@code this} has no extensions
     1053 +     * @since 1.8
     1054 +     */
     1055 +    public Locale stripExtensions() {
     1056 +        return hasExtensions() ? Locale.getInstance(baseLocale, null) : this;
     1057 +    }
     1058 +
     1059 +    /**
1037 1060       * Returns the extension (or private use) value associated with
1038 1061       * the specified key, or null if there is no extension
1039 1062       * associated with the key. To be well-formed, the key must be one
1040 1063       * of <code>[0-9A-Za-z]</code>. Keys are case-insensitive, so
1041 1064       * for example 'z' and 'Z' represent the same extension.
1042 1065       *
1043 1066       * @param key the extension key
1044 1067       * @return The extension, or null if this locale defines no
1045 1068       * extension for the specified key.
1046 1069       * @throws IllegalArgumentException if key is not well-formed
1047 1070       * @see #PRIVATE_USE_EXTENSION
1048 1071       * @see #UNICODE_LOCALE_EXTENSION
1049 1072       * @since 1.7
1050 1073       */
1051 1074      public String getExtension(char key) {
1052 1075          if (!LocaleExtensions.isValidKey(key)) {
1053 1076              throw new IllegalArgumentException("Ill-formed extension key: " + key);
1054 1077          }
1055      -        return (localeExtensions == null) ? null : localeExtensions.getExtensionValue(key);
     1078 +        return hasExtensions() ? localeExtensions.getExtensionValue(key) : null;
1056 1079      }
1057 1080  
1058 1081      /**
1059 1082       * Returns the set of extension keys associated with this locale, or the
1060 1083       * empty set if it has no extensions. The returned set is unmodifiable.
1061 1084       * The keys will all be lower-case.
1062 1085       *
1063 1086       * @return The set of extension keys, or the empty set if this locale has
1064 1087       * no extensions.
1065 1088       * @since 1.7
1066 1089       */
1067 1090      public Set<Character> getExtensionKeys() {
1068      -        if (localeExtensions == null) {
     1091 +        if (!hasExtensions()) {
1069 1092              return Collections.emptySet();
1070 1093          }
1071 1094          return localeExtensions.getKeys();
1072 1095      }
1073 1096  
1074 1097      /**
1075 1098       * Returns the set of unicode locale attributes associated with
1076 1099       * this locale, or the empty set if it has no attributes. The
1077 1100       * returned set is unmodifiable.
1078 1101       *
1079 1102       * @return The set of attributes.
1080 1103       * @since 1.7
1081 1104       */
1082 1105      public Set<String> getUnicodeLocaleAttributes() {
1083      -        if (localeExtensions == null) {
     1106 +        if (!hasExtensions()) {
1084 1107              return Collections.emptySet();
1085 1108          }
1086 1109          return localeExtensions.getUnicodeLocaleAttributes();
1087 1110      }
1088 1111  
1089 1112      /**
1090 1113       * Returns the Unicode locale type associated with the specified Unicode locale key
1091 1114       * for this locale. Returns the empty string for keys that are defined with no type.
1092 1115       * Returns null if the key is not defined. Keys are case-insensitive. The key must
1093 1116       * be two alphanumeric characters ([0-9a-zA-Z]), or an IllegalArgumentException is
1094 1117       * thrown.
1095 1118       *
1096 1119       * @param key the Unicode locale key
1097 1120       * @return The Unicode locale type associated with the key, or null if the
1098 1121       * locale does not define the key.
1099 1122       * @throws IllegalArgumentException if the key is not well-formed
1100 1123       * @throws NullPointerException if <code>key</code> is null
1101 1124       * @since 1.7
1102 1125       */
1103 1126      public String getUnicodeLocaleType(String key) {
1104      -        if (!UnicodeLocaleExtension.isKey(key)) {
     1127 +        if (!isUnicodeExtensionKey(key)) {
1105 1128              throw new IllegalArgumentException("Ill-formed Unicode locale key: " + key);
1106 1129          }
1107      -        return (localeExtensions == null) ? null : localeExtensions.getUnicodeLocaleType(key);
     1130 +        return hasExtensions() ? localeExtensions.getUnicodeLocaleType(key) : null;
1108 1131      }
1109 1132  
1110 1133      /**
1111 1134       * Returns the set of Unicode locale keys defined by this locale, or the empty set if
1112 1135       * this locale has none.  The returned set is immutable.  Keys are all lower case.
1113 1136       *
1114 1137       * @return The set of Unicode locale keys, or the empty set if this locale has
1115 1138       * no Unicode locale keywords.
1116 1139       * @since 1.7
1117 1140       */
↓ open down ↓ 160 lines elided ↑ open up ↑
1278 1301       *
1279 1302       * will return "xx-YY", but the language subtag "xx" and the
1280 1303       * region subtag "YY" are invalid because they are not registered
1281 1304       * in the IANA Language Subtag Registry.
1282 1305       *
1283 1306       * @return a BCP47 language tag representing the locale
1284 1307       * @see #forLanguageTag(String)
1285 1308       * @since 1.7
1286 1309       */
1287 1310      public String toLanguageTag() {
     1311 +        if (languageTag != null) {
     1312 +            return languageTag;
     1313 +        }
     1314 +
1288 1315          LanguageTag tag = LanguageTag.parseLocale(baseLocale, localeExtensions);
1289 1316          StringBuilder buf = new StringBuilder();
1290 1317  
1291 1318          String subtag = tag.getLanguage();
1292 1319          if (subtag.length() > 0) {
1293 1320              buf.append(LanguageTag.canonicalizeLanguage(subtag));
1294 1321          }
1295 1322  
1296 1323          subtag = tag.getScript();
1297 1324          if (subtag.length() > 0) {
↓ open down ↓ 23 lines elided ↑ open up ↑
1321 1348          subtag = tag.getPrivateuse();
1322 1349          if (subtag.length() > 0) {
1323 1350              if (buf.length() > 0) {
1324 1351                  buf.append(LanguageTag.SEP);
1325 1352              }
1326 1353              buf.append(LanguageTag.PRIVATEUSE).append(LanguageTag.SEP);
1327 1354              // preserve casing
1328 1355              buf.append(subtag);
1329 1356          }
1330 1357  
1331      -        return buf.toString();
     1358 +        String langTag = buf.toString();
     1359 +        synchronized (this) {
     1360 +            if (languageTag == null) {
     1361 +                languageTag = langTag;
     1362 +            }
     1363 +        }
     1364 +        return languageTag;
1332 1365      }
1333 1366  
1334 1367      /**
1335 1368       * Returns a locale for the specified IETF BCP 47 language tag string.
1336 1369       *
1337 1370       * <p>If the specified language tag contains any ill-formed subtags,
1338 1371       * the first such subtag and all following subtags are ignored.  Compare
1339 1372       * to {@link Locale.Builder#setLanguageTag} which throws an exception
1340 1373       * in this case.
1341 1374       *
↓ open down ↓ 165 lines elided ↑ open up ↑
1507 1540       */
1508 1541      public String getISO3Country() throws MissingResourceException {
1509 1542          String country3 = getISO3Code(baseLocale.getRegion(), LocaleISOData.isoCountryTable);
1510 1543          if (country3 == null) {
1511 1544              throw new MissingResourceException("Couldn't find 3-letter country code for "
1512 1545                      + baseLocale.getRegion(), "FormatData_" + toString(), "ShortCountry");
1513 1546          }
1514 1547          return country3;
1515 1548      }
1516 1549  
1517      -    private static final String getISO3Code(String iso2Code, String table) {
     1550 +    private static String getISO3Code(String iso2Code, String table) {
1518 1551          int codeLength = iso2Code.length();
1519 1552          if (codeLength == 0) {
1520 1553              return "";
1521 1554          }
1522 1555  
1523 1556          int tableLength = table.length();
1524 1557          int index = tableLength;
1525 1558          if (codeLength == 2) {
1526 1559              char c1 = iso2Code.charAt(0);
1527 1560              char c2 = iso2Code.charAt(1);
↓ open down ↓ 105 lines elided ↑ open up ↑
1633 1666  
1634 1667      private String getDisplayString(String code, Locale inLocale, int type) {
1635 1668          if (code.length() == 0) {
1636 1669              return "";
1637 1670          }
1638 1671  
1639 1672          if (inLocale == null) {
1640 1673              throw new NullPointerException();
1641 1674          }
1642 1675  
1643      -        try {
1644      -            OpenListResourceBundle bundle = LocaleData.getLocaleNames(inLocale);
1645      -            String key = (type == DISPLAY_VARIANT ? "%%"+code : code);
1646      -            String result = null;
1647      -
1648      -            // Check whether a provider can provide an implementation that's closer
1649      -            // to the requested locale than what the Java runtime itself can provide.
1650      -            LocaleServiceProviderPool pool =
1651      -                LocaleServiceProviderPool.getPool(LocaleNameProvider.class);
1652      -            if (pool.hasProviders()) {
1653      -                result = pool.getLocalizedObject(
1654      -                                    LocaleNameGetter.INSTANCE,
1655      -                                    inLocale, bundle, key,
1656      -                                    type, code);
1657      -            }
1658      -
1659      -            if (result == null) {
1660      -                result = bundle.getString(key);
1661      -            }
1662      -
     1676 +        LocaleServiceProviderPool pool =
     1677 +            LocaleServiceProviderPool.getPool(LocaleNameProvider.class);
     1678 +        String key = (type == DISPLAY_VARIANT ? "%%"+code : code);
     1679 +        String result = pool.getLocalizedObject(
     1680 +                                LocaleNameGetter.INSTANCE,
     1681 +                                inLocale, key, type, code);
1663 1682              if (result != null) {
1664 1683                  return result;
1665 1684              }
1666      -        }
1667      -        catch (Exception e) {
1668      -            // just fall through
1669      -        }
     1685 +
1670 1686          return code;
1671 1687      }
1672 1688  
1673 1689      /**
1674 1690       * Returns a name for the locale's variant code that is appropriate for display to the
1675 1691       * user.  If possible, the name will be localized for the default locale.  If the locale
1676 1692       * doesn't specify a variant code, this function returns the empty string.
1677 1693       */
1678 1694      public final String getDisplayVariant() {
1679 1695          return getDisplayVariant(getDefault(Category.DISPLAY));
↓ open down ↓ 3 lines elided ↑ open up ↑
1683 1699       * Returns a name for the locale's variant code that is appropriate for display to the
1684 1700       * user.  If possible, the name will be localized for inLocale.  If the locale
1685 1701       * doesn't specify a variant code, this function returns the empty string.
1686 1702       *
1687 1703       * @exception NullPointerException if <code>inLocale</code> is <code>null</code>
1688 1704       */
1689 1705      public String getDisplayVariant(Locale inLocale) {
1690 1706          if (baseLocale.getVariant().length() == 0)
1691 1707              return "";
1692 1708  
1693      -        OpenListResourceBundle bundle = LocaleData.getLocaleNames(inLocale);
     1709 +        OpenListResourceBundle bundle = LocaleProviderAdapter.forJRE().getLocaleData().getLocaleNames(inLocale);
1694 1710  
1695 1711          String names[] = getDisplayVariantArray(bundle, inLocale);
1696 1712  
1697 1713          // Get the localized patterns for formatting a list, and use
1698 1714          // them to format the list.
1699 1715          String listPattern = null;
1700 1716          String listCompositionPattern = null;
1701 1717          try {
1702 1718              listPattern = bundle.getString("ListPattern");
1703 1719              listCompositionPattern = bundle.getString("ListCompositionPattern");
↓ open down ↓ 37 lines elided ↑ open up ↑
1741 1757       * script (country)<br>
1742 1758       * country<br>
1743 1759       * </blockquote>
1744 1760       * depending on which fields are specified in the locale.  If the
1745 1761       * language, script, country, and variant fields are all empty,
1746 1762       * this function returns the empty string.
1747 1763       *
1748 1764       * @throws NullPointerException if <code>inLocale</code> is <code>null</code>
1749 1765       */
1750 1766      public String getDisplayName(Locale inLocale) {
1751      -        OpenListResourceBundle bundle = LocaleData.getLocaleNames(inLocale);
     1767 +        OpenListResourceBundle bundle = LocaleProviderAdapter.forJRE().getLocaleData().getLocaleNames(inLocale);
1752 1768  
1753 1769          String languageName = getDisplayLanguage(inLocale);
1754 1770          String scriptName = getDisplayScript(inLocale);
1755 1771          String countryName = getDisplayCountry(inLocale);
1756 1772          String[] variantNames = getDisplayVariantArray(bundle, inLocale);
1757 1773  
1758 1774          // Get the localized patterns for formatting a display name.
1759 1775          String displayNamePattern = null;
1760 1776          String listPattern = null;
1761 1777          String listCompositionPattern = null;
↓ open down ↓ 25 lines elided ↑ open up ↑
1787 1803          if (languageName.length() != 0) {
1788 1804              names.add(languageName);
1789 1805          }
1790 1806          if (scriptName.length() != 0) {
1791 1807              names.add(scriptName);
1792 1808          }
1793 1809          if (countryName.length() != 0) {
1794 1810              names.add(countryName);
1795 1811          }
1796 1812          if (variantNames.length != 0) {
1797      -            for (String var : variantNames) {
1798      -                names.add(var);
1799      -            }
     1813 +            names.addAll(Arrays.asList(variantNames));
1800 1814          }
1801 1815  
1802 1816          // The first one in the main name
1803 1817          mainName = names.get(0);
1804 1818  
1805 1819          // Others are qualifiers
1806 1820          int numNames = names.size();
1807 1821          qualifierNames = (numNames > 1) ?
1808 1822                  names.subList(1, numNames).toArray(new String[numNames - 1]) : new String[0];
1809 1823  
↓ open down ↓ 26 lines elided ↑ open up ↑
1836 1850                  result.append((String)displayNames[2]);
1837 1851                  result.append(')');
1838 1852              }
1839 1853              return result.toString();
1840 1854          }
1841 1855      }
1842 1856  
1843 1857      /**
1844 1858       * Overrides Cloneable.
1845 1859       */
     1860 +    @Override
1846 1861      public Object clone()
1847 1862      {
1848 1863          try {
1849 1864              Locale that = (Locale)super.clone();
1850 1865              return that;
1851 1866          } catch (CloneNotSupportedException e) {
1852 1867              throw new InternalError(e);
1853 1868          }
1854 1869      }
1855 1870  
↓ open down ↓ 47 lines elided ↑ open up ↑
1903 1918  
1904 1919      /**
1905 1920       * Calculated hashcode
1906 1921       */
1907 1922      private transient volatile int hashCodeValue = 0;
1908 1923  
1909 1924      private volatile static Locale defaultLocale = initDefault();
1910 1925      private volatile static Locale defaultDisplayLocale = null;
1911 1926      private volatile static Locale defaultFormatLocale = null;
1912 1927  
     1928 +    private transient volatile String languageTag;
     1929 +
1913 1930      /**
1914 1931       * Return an array of the display names of the variant.
1915 1932       * @param bundle the ResourceBundle to use to get the display names
1916 1933       * @return an array of display names, possible of zero length.
1917 1934       */
1918 1935      private String[] getDisplayVariantArray(OpenListResourceBundle bundle, Locale inLocale) {
1919 1936          // Split the variant name into tokens separated by '_'.
1920 1937          StringTokenizer tokenizer = new StringTokenizer(baseLocale.getVariant(), "_");
1921 1938          String[] names = new String[tokenizer.countTokens()];
1922 1939  
↓ open down ↓ 15 lines elided ↑ open up ↑
1938 1955       * @param listPattern should create a MessageFormat taking 0-3 arguments
1939 1956       * and formatting them into a list.
1940 1957       * @param listCompositionPattern should take 2 arguments
1941 1958       * and is used by composeList.
1942 1959       * @return a string representing the list.
1943 1960       */
1944 1961      private static String formatList(String[] stringList, String listPattern, String listCompositionPattern) {
1945 1962          // If we have no list patterns, compose the list in a simple,
1946 1963          // non-localized way.
1947 1964          if (listPattern == null || listCompositionPattern == null) {
1948      -            StringBuffer result = new StringBuffer();
1949      -            for (int i=0; i<stringList.length; ++i) {
1950      -                if (i>0) result.append(',');
     1965 +            StringBuilder result = new StringBuilder();
     1966 +            for (int i = 0; i < stringList.length; ++i) {
     1967 +                if (i > 0) {
     1968 +                    result.append(',');
     1969 +                }
1951 1970                  result.append(stringList[i]);
1952 1971              }
1953 1972              return result.toString();
1954 1973          }
1955 1974  
1956 1975          // Compose the list down to three elements if necessary
1957 1976          if (stringList.length > 3) {
1958 1977              MessageFormat format = new MessageFormat(listCompositionPattern);
1959 1978              stringList = composeList(format, stringList);
1960 1979          }
↓ open down ↓ 26 lines elided ↑ open up ↑
1987 2006  
1988 2007          // Form a new list one element shorter
1989 2008          String[] newList = new String[list.length-1];
1990 2009          System.arraycopy(list, 2, newList, 1, newList.length-1);
1991 2010          newList[0] = newItem;
1992 2011  
1993 2012          // Recurse
1994 2013          return composeList(format, newList);
1995 2014      }
1996 2015  
     2016 +    // Duplicate of sun.util.locale.UnicodeLocaleExtension.isKey in order to
     2017 +    // avoid its class loading.
     2018 +    private static boolean isUnicodeExtensionKey(String s) {
     2019 +        // 2alphanum
     2020 +        return (s.length() == 2) && LocaleUtils.isAlphaNumericString(s);
     2021 +    }
     2022 +
1997 2023      /**
1998 2024       * @serialField language    String
1999 2025       *      language subtag in lower case. (See <a href="java/util/Locale.html#getLanguage()">getLanguage()</a>)
2000 2026       * @serialField country     String
2001 2027       *      country subtag in upper case. (See <a href="java/util/Locale.html#getCountry()">getCountry()</a>)
2002 2028       * @serialField variant     String
2003 2029       *      variant subtags separated by LOWLINE characters. (See <a href="java/util/Locale.html#getVariant()">getVariant()</a>)
2004 2030       * @serialField hashcode    int
2005 2031       *      deprecated, for forward compatibility only
2006 2032       * @serialField script      String
↓ open down ↓ 122 lines elided ↑ open up ↑
2129 2155      }
2130 2156  
2131 2157      /**
2132 2158       * Obtains a localized locale names from a LocaleNameProvider
2133 2159       * implementation.
2134 2160       */
2135 2161      private static class LocaleNameGetter
2136 2162          implements LocaleServiceProviderPool.LocalizedObjectGetter<LocaleNameProvider, String> {
2137 2163          private static final LocaleNameGetter INSTANCE = new LocaleNameGetter();
2138 2164  
     2165 +        @Override
2139 2166          public String getObject(LocaleNameProvider localeNameProvider,
2140 2167                                  Locale locale,
2141 2168                                  String key,
2142 2169                                  Object... params) {
2143 2170              assert params.length == 2;
2144 2171              int type = (Integer)params[0];
2145 2172              String code = (String)params[1];
2146 2173  
2147 2174              switch(type) {
2148 2175              case DISPLAY_LANGUAGE:
↓ open down ↓ 402 lines elided ↑ open up ↑
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX