4060 lines
167 KiB
Java
4060 lines
167 KiB
Java
![]() |
/* GENERATED SOURCE. DO NOT MODIFY. */
|
||
|
// © 2016 and later: Unicode, Inc. and others.
|
||
|
// License & terms of use: http://www.unicode.org/copyright.html
|
||
|
/*
|
||
|
******************************************************************************
|
||
|
* Copyright (C) 2003-2016, International Business Machines Corporation and
|
||
|
* others. All Rights Reserved.
|
||
|
******************************************************************************
|
||
|
*/
|
||
|
|
||
|
package android.icu.util;
|
||
|
|
||
|
import java.io.Serializable;
|
||
|
import java.lang.reflect.InvocationTargetException;
|
||
|
import java.lang.reflect.Method;
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.Arrays;
|
||
|
import java.util.Collection;
|
||
|
import java.util.Collections;
|
||
|
import java.util.HashMap;
|
||
|
import java.util.HashSet;
|
||
|
import java.util.Iterator;
|
||
|
import java.util.List;
|
||
|
import java.util.Locale;
|
||
|
import java.util.Map;
|
||
|
import java.util.Map.Entry;
|
||
|
import java.util.MissingResourceException;
|
||
|
import java.util.Objects;
|
||
|
import java.util.Set;
|
||
|
import java.util.TreeMap;
|
||
|
import java.util.TreeSet;
|
||
|
|
||
|
import android.icu.impl.CacheBase;
|
||
|
import android.icu.impl.ICUData;
|
||
|
import android.icu.impl.ICUResourceBundle;
|
||
|
import android.icu.impl.ICUResourceTableAccess;
|
||
|
import android.icu.impl.LocaleIDParser;
|
||
|
import android.icu.impl.LocaleIDs;
|
||
|
import android.icu.impl.SoftCache;
|
||
|
import android.icu.impl.Utility;
|
||
|
import android.icu.impl.locale.AsciiUtil;
|
||
|
import android.icu.impl.locale.BaseLocale;
|
||
|
import android.icu.impl.locale.Extension;
|
||
|
import android.icu.impl.locale.InternalLocaleBuilder;
|
||
|
import android.icu.impl.locale.KeyTypeData;
|
||
|
import android.icu.impl.locale.LSR;
|
||
|
import android.icu.impl.locale.LanguageTag;
|
||
|
import android.icu.impl.locale.LikelySubtags;
|
||
|
import android.icu.impl.locale.LocaleExtensions;
|
||
|
import android.icu.impl.locale.LocaleSyntaxException;
|
||
|
import android.icu.impl.locale.ParseStatus;
|
||
|
import android.icu.impl.locale.UnicodeLocaleExtension;
|
||
|
import android.icu.lang.UScript;
|
||
|
import android.icu.text.LocaleDisplayNames;
|
||
|
import android.icu.text.LocaleDisplayNames.DialectHandling;
|
||
|
/**
|
||
|
* <strong>[icu enhancement]</strong> ICU's replacement for {@link java.util.Locale}. Methods, fields, and other functionality specific to ICU are labeled '<strong>[icu]</strong>'.
|
||
|
*
|
||
|
* A class analogous to {@link java.util.Locale} that provides additional
|
||
|
* support for ICU protocol. In ICU 3.0 this class is enhanced to support
|
||
|
* RFC 3066 language identifiers.
|
||
|
*
|
||
|
* <p>Many classes and services in ICU follow a factory idiom, in
|
||
|
* which a factory method or object responds to a client request with
|
||
|
* an object. The request includes a locale (the <i>requested</i>
|
||
|
* locale), and the returned object is constructed using data for that
|
||
|
* locale. The system may lack data for the requested locale, in
|
||
|
* which case the locale fallback mechanism will be invoked until a
|
||
|
* populated locale is found (the <i>valid</i> locale). Furthermore,
|
||
|
* even when a populated locale is found (the <i>valid</i> locale),
|
||
|
* further fallback may be required to reach a locale containing the
|
||
|
* specific data required by the service (the <i>actual</i> locale).
|
||
|
*
|
||
|
* <p>ULocale performs <b>'normalization'</b> and <b>'canonicalization'</b> of locale ids.
|
||
|
* Normalization 'cleans up' ICU locale ids as follows:
|
||
|
* <ul>
|
||
|
* <li>language, script, country, variant, and keywords are properly cased<br>
|
||
|
* (lower, title, upper, upper, and lower case respectively)</li>
|
||
|
* <li>hyphens used as separators are converted to underscores</li>
|
||
|
* <li>three-letter language and country ids are converted to two-letter
|
||
|
* equivalents where available</li>
|
||
|
* <li>surrounding spaces are removed from keywords and values</li>
|
||
|
* <li>if there are multiple keywords, they are put in sorted order</li>
|
||
|
* </ul>
|
||
|
* Canonicalization additionally performs the following:
|
||
|
* <ul>
|
||
|
* <li>POSIX ids are converted to ICU format IDs</li>
|
||
|
* <li>Legacy language tags (marked as “Type: grandfathered” in BCP 47)
|
||
|
* are converted to ICU standard form</li>
|
||
|
* </ul>
|
||
|
* All ULocale constructors automatically normalize the locale id. To handle
|
||
|
* POSIX ids, <code>canonicalize</code> can be called to convert the id
|
||
|
* to canonical form, or the <code>canonicalInstance</code> factory method
|
||
|
* can be called.
|
||
|
*
|
||
|
* <p>Note: The <i>actual</i> locale is returned correctly, but the <i>valid</i>
|
||
|
* locale is not, in most cases.
|
||
|
*
|
||
|
* @see java.util.Locale
|
||
|
* @author weiv
|
||
|
* @author Alan Liu
|
||
|
* @author Ram Viswanadha
|
||
|
*/
|
||
|
@SuppressWarnings("javadoc") // android.icu.text.Collator is in another project
|
||
|
public final class ULocale implements Serializable, Comparable<ULocale> {
|
||
|
// using serialver from jdk1.4.2_05
|
||
|
private static final long serialVersionUID = 3715177670352309217L;
|
||
|
|
||
|
private static CacheBase<String, String, Void> nameCache = new SoftCache<String, String, Void>() {
|
||
|
@Override
|
||
|
protected String createInstance(String tmpLocaleID, Void unused) {
|
||
|
return new LocaleIDParser(tmpLocaleID).getName();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Types for {@link ULocale#getAvailableLocalesByType}
|
||
|
*/
|
||
|
public static enum AvailableType {
|
||
|
/**
|
||
|
* Locales that return data when passed to ICU APIs,
|
||
|
* but not including legacy or alias locales.
|
||
|
*/
|
||
|
DEFAULT,
|
||
|
|
||
|
/**
|
||
|
* Legacy or alias locales that return data when passed to ICU APIs.
|
||
|
* Examples of supported legacy or alias locales:
|
||
|
*
|
||
|
* <ul>
|
||
|
* <li>iw (alias to he)
|
||
|
* <li>mo (alias to ro)
|
||
|
* <li>zh_CN (alias to zh_Hans_CN)
|
||
|
* <li>sr_BA (alias to sr_Cyrl_BA)
|
||
|
* <li>ars (alias to ar_SA)
|
||
|
* </ul>
|
||
|
*
|
||
|
* The locales in this set are disjoint from the ones in
|
||
|
* DEFAULT. To get both sets at the same time, use
|
||
|
* WITH_LEGACY_ALIASES.
|
||
|
*/
|
||
|
ONLY_LEGACY_ALIASES,
|
||
|
|
||
|
/**
|
||
|
* The union of the locales in DEFAULT and ONLY_LEGACY_ALIASES.
|
||
|
*/
|
||
|
WITH_LEGACY_ALIASES,
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Useful constant for language.
|
||
|
*/
|
||
|
public static final ULocale ENGLISH = new ULocale("en", Locale.ENGLISH);
|
||
|
|
||
|
/**
|
||
|
* Useful constant for language.
|
||
|
*/
|
||
|
public static final ULocale FRENCH = new ULocale("fr", Locale.FRENCH);
|
||
|
|
||
|
/**
|
||
|
* Useful constant for language.
|
||
|
*/
|
||
|
public static final ULocale GERMAN = new ULocale("de", Locale.GERMAN);
|
||
|
|
||
|
/**
|
||
|
* Useful constant for language.
|
||
|
*/
|
||
|
public static final ULocale ITALIAN = new ULocale("it", Locale.ITALIAN);
|
||
|
|
||
|
/**
|
||
|
* Useful constant for language.
|
||
|
*/
|
||
|
public static final ULocale JAPANESE = new ULocale("ja", Locale.JAPANESE);
|
||
|
|
||
|
/**
|
||
|
* Useful constant for language.
|
||
|
*/
|
||
|
public static final ULocale KOREAN = new ULocale("ko", Locale.KOREAN);
|
||
|
|
||
|
/**
|
||
|
* Useful constant for language.
|
||
|
*/
|
||
|
public static final ULocale CHINESE = new ULocale("zh", Locale.CHINESE);
|
||
|
|
||
|
|
||
|
// Special note about static initializer for
|
||
|
// - SIMPLIFIED_CHINESE
|
||
|
// - TRADTIONAL_CHINESE
|
||
|
// - CHINA
|
||
|
// - TAIWAN
|
||
|
//
|
||
|
// Equivalent JDK Locale for ULocale.SIMPLIFIED_CHINESE is different
|
||
|
// by JRE version. JRE 7 or later supports a script tag "Hans", while
|
||
|
// JRE 6 or older does not. JDK's Locale.SIMPLIFIED_CHINESE is actually
|
||
|
// zh_CN, not zh_Hans. This is same in Java 7 or later versions.
|
||
|
//
|
||
|
// ULocale#toLocale() implementation create a Locale with a script tag.
|
||
|
// When a new ULocale is constructed with the single arg
|
||
|
// constructor, the volatile field 'Locale locale' is initialized by
|
||
|
// #toLocale() method.
|
||
|
//
|
||
|
// Because we cannot hardcode corresponding JDK Locale representation below,
|
||
|
// SIMPLIFIED_CHINESE is constructed without JDK Locale argument, and
|
||
|
// #toLocale() is used for resolving the best matching JDK Locale at runtime.
|
||
|
//
|
||
|
// The same thing applies to TRADITIONAL_CHINESE.
|
||
|
|
||
|
/**
|
||
|
* Useful constant for language.
|
||
|
*/
|
||
|
public static final ULocale SIMPLIFIED_CHINESE = new ULocale("zh_Hans");
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Useful constant for language.
|
||
|
*/
|
||
|
public static final ULocale TRADITIONAL_CHINESE = new ULocale("zh_Hant");
|
||
|
|
||
|
/**
|
||
|
* Useful constant for country/region.
|
||
|
*/
|
||
|
public static final ULocale FRANCE = new ULocale("fr_FR", Locale.FRANCE);
|
||
|
|
||
|
/**
|
||
|
* Useful constant for country/region.
|
||
|
*/
|
||
|
public static final ULocale GERMANY = new ULocale("de_DE", Locale.GERMANY);
|
||
|
|
||
|
/**
|
||
|
* Useful constant for country/region.
|
||
|
*/
|
||
|
public static final ULocale ITALY = new ULocale("it_IT", Locale.ITALY);
|
||
|
|
||
|
/**
|
||
|
* Useful constant for country/region.
|
||
|
*/
|
||
|
public static final ULocale JAPAN = new ULocale("ja_JP", Locale.JAPAN);
|
||
|
|
||
|
/**
|
||
|
* Useful constant for country/region.
|
||
|
*/
|
||
|
public static final ULocale KOREA = new ULocale("ko_KR", Locale.KOREA);
|
||
|
|
||
|
/**
|
||
|
* Useful constant for country/region.
|
||
|
*/
|
||
|
public static final ULocale CHINA = new ULocale("zh_Hans_CN");
|
||
|
|
||
|
/**
|
||
|
* Useful constant for country/region.
|
||
|
*/
|
||
|
public static final ULocale PRC = CHINA;
|
||
|
|
||
|
/**
|
||
|
* Useful constant for country/region.
|
||
|
*/
|
||
|
public static final ULocale TAIWAN = new ULocale("zh_Hant_TW");
|
||
|
|
||
|
/**
|
||
|
* Useful constant for country/region.
|
||
|
*/
|
||
|
public static final ULocale UK = new ULocale("en_GB", Locale.UK);
|
||
|
|
||
|
/**
|
||
|
* Useful constant for country/region.
|
||
|
*/
|
||
|
public static final ULocale US = new ULocale("en_US", Locale.US);
|
||
|
|
||
|
/**
|
||
|
* Useful constant for country/region.
|
||
|
*/
|
||
|
public static final ULocale CANADA = new ULocale("en_CA", Locale.CANADA);
|
||
|
|
||
|
/**
|
||
|
* Useful constant for country/region.
|
||
|
*/
|
||
|
public static final ULocale CANADA_FRENCH = new ULocale("fr_CA", Locale.CANADA_FRENCH);
|
||
|
|
||
|
/**
|
||
|
* Handy constant.
|
||
|
*/
|
||
|
private static final String EMPTY_STRING = "";
|
||
|
|
||
|
// Used in both ULocale and LocaleIDParser, so moved up here.
|
||
|
private static final char UNDERSCORE = '_';
|
||
|
|
||
|
// default empty locale
|
||
|
private static final Locale EMPTY_LOCALE = new Locale("", "");
|
||
|
|
||
|
// special keyword key for Unicode locale attributes
|
||
|
private static final String LOCALE_ATTRIBUTE_KEY = "attribute";
|
||
|
|
||
|
/**
|
||
|
* The root ULocale.
|
||
|
*/
|
||
|
public static final ULocale ROOT = new ULocale("", EMPTY_LOCALE);
|
||
|
|
||
|
/**
|
||
|
* Enum for locale categories. These locale categories are used to get/set the default locale for
|
||
|
* the specific functionality represented by the category.
|
||
|
*/
|
||
|
public enum Category {
|
||
|
/**
|
||
|
* Category used to represent the default locale for displaying user interfaces.
|
||
|
*/
|
||
|
DISPLAY,
|
||
|
/**
|
||
|
* Category used to represent the default locale for formatting date, number and/or currency.
|
||
|
*/
|
||
|
FORMAT
|
||
|
}
|
||
|
|
||
|
private static final SoftCache<Locale, ULocale, Void> CACHE = new SoftCache<Locale, ULocale, Void>() {
|
||
|
@Override
|
||
|
protected ULocale createInstance(Locale key, Void unused) {
|
||
|
return JDKLocaleHelper.toULocale(key);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Cache the locale.
|
||
|
*/
|
||
|
private transient volatile Locale locale;
|
||
|
|
||
|
/**
|
||
|
* The raw localeID that we were passed in.
|
||
|
*/
|
||
|
private String localeID;
|
||
|
|
||
|
/**
|
||
|
* Cache the locale data container fields.
|
||
|
* In future, we want to use them as the primary locale identifier storage.
|
||
|
*/
|
||
|
private transient volatile BaseLocale baseLocale;
|
||
|
private transient volatile LocaleExtensions extensions;
|
||
|
|
||
|
/**
|
||
|
* This table lists pairs of locale ids for canonicalization.
|
||
|
* The 1st item is the normalized id. The 2nd item is the
|
||
|
* canonicalized id.
|
||
|
*/
|
||
|
private static String[][] CANONICALIZE_MAP = {
|
||
|
{ "art__LOJBAN", "jbo" }, /* registered name */
|
||
|
{ "cel__GAULISH", "cel__GAULISH" }, /* registered name */
|
||
|
{ "de__1901", "de__1901" }, /* registered name */
|
||
|
{ "de__1906", "de__1906" }, /* registered name */
|
||
|
{ "en__BOONT", "en__BOONT" }, /* registered name */
|
||
|
{ "en__SCOUSE", "en__SCOUSE" }, /* registered name */
|
||
|
{ "hy__AREVELA", "hy", null, null }, /* Registered IANA variant */
|
||
|
{ "hy__AREVMDA", "hyw", null, null }, /* Registered IANA variant */
|
||
|
{ "sl__ROZAJ", "sl__ROZAJ" }, /* registered name */
|
||
|
{ "zh__GUOYU", "zh" }, /* registered name */
|
||
|
{ "zh__HAKKA", "hak" }, /* registered name */
|
||
|
{ "zh__XIANG", "hsn" }, /* registered name */
|
||
|
// Three letter subtags won't be treated as variants.
|
||
|
{ "zh_GAN", "gan" }, /* registered name */
|
||
|
{ "zh_MIN", "zh__MIN" }, /* registered name */
|
||
|
{ "zh_MIN_NAN", "nan" }, /* registered name */
|
||
|
{ "zh_WUU", "wuu" }, /* registered name */
|
||
|
{ "zh_YUE", "yue" } /* registered name */
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Private constructor used by static initializers.
|
||
|
*/
|
||
|
private ULocale(String localeID, Locale locale) {
|
||
|
this.localeID = localeID;
|
||
|
this.locale = locale;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a ULocale object for a {@link java.util.Locale}.
|
||
|
* The ULocale is canonicalized.
|
||
|
* @param loc a {@link java.util.Locale}
|
||
|
*/
|
||
|
public static ULocale forLocale(Locale loc) {
|
||
|
if (loc == null) {
|
||
|
return null;
|
||
|
}
|
||
|
return CACHE.getInstance(loc, null /* unused */);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Constructs a ULocale from a RFC 3066 locale ID. The locale ID consists
|
||
|
* of optional language, script, country, and variant fields in that order,
|
||
|
* separated by underscores, followed by an optional keyword list. The
|
||
|
* script, if present, is four characters long-- this distinguishes it
|
||
|
* from a country code, which is two characters long. Other fields
|
||
|
* are distinguished by position as indicated by the underscores. The
|
||
|
* start of the keyword list is indicated by '@', and consists of two
|
||
|
* or more keyword/value pairs separated by semicolons(';').
|
||
|
*
|
||
|
* <p>This constructor does not canonicalize the localeID. So, for
|
||
|
* example, "zh__pinyin" remains unchanged instead of converting
|
||
|
* to "zh@collation=pinyin". By default ICU only recognizes the
|
||
|
* latter as specifying pinyin collation. Use {@link #createCanonical}
|
||
|
* or {@link #canonicalize} if you need to canonicalize the localeID.
|
||
|
*
|
||
|
* @param localeID string representation of the locale, e.g:
|
||
|
* "en_US", "sy_Cyrl_YU", "zh__pinyin", "es_ES@currency=EUR;collation=traditional"
|
||
|
*/
|
||
|
public ULocale(String localeID) {
|
||
|
this.localeID = getName(localeID);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convenience overload of ULocale(String, String, String) for
|
||
|
* compatibility with java.util.Locale.
|
||
|
* @see #ULocale(String, String, String)
|
||
|
*/
|
||
|
public ULocale(String a, String b) {
|
||
|
this(a, b, null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Constructs a ULocale from a localeID constructed from the three 'fields' a, b, and
|
||
|
* c. These fields are concatenated using underscores to form a localeID of the form
|
||
|
* a_b_c, which is then handled like the localeID passed to <code>ULocale(String
|
||
|
* localeID)</code>.
|
||
|
*
|
||
|
* <p>Java locale strings consisting of language, country, and
|
||
|
* variant will be handled by this form, since the country code
|
||
|
* (being shorter than four letters long) will not be interpreted
|
||
|
* as a script code. If a script code is present, the final
|
||
|
* argument ('c') will be interpreted as the country code. It is
|
||
|
* recommended that this constructor only be used to ease porting,
|
||
|
* and that clients instead use the single-argument constructor
|
||
|
* when constructing a ULocale from a localeID.
|
||
|
* @param a first component of the locale id
|
||
|
* @param b second component of the locale id
|
||
|
* @param c third component of the locale id
|
||
|
* @see #ULocale(String)
|
||
|
*/
|
||
|
public ULocale(String a, String b, String c) {
|
||
|
localeID = getName(lscvToID(a, b, c, EMPTY_STRING));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Creates a ULocale from the id by first canonicalizing the id according to CLDR.
|
||
|
* @param nonCanonicalID the locale id to canonicalize
|
||
|
* @return the locale created from the canonical version of the ID.
|
||
|
*/
|
||
|
public static ULocale createCanonical(String nonCanonicalID) {
|
||
|
return new ULocale(canonicalize(nonCanonicalID), (Locale)null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a ULocale from the locale by first canonicalizing the locale according to CLDR.
|
||
|
* @param locale the ULocale to canonicalize
|
||
|
* @return the ULocale created from the canonical version of the ULocale.
|
||
|
*/
|
||
|
public static ULocale createCanonical(ULocale locale) {
|
||
|
return createCanonical(locale.getName());
|
||
|
}
|
||
|
|
||
|
private static String lscvToID(String lang, String script, String country, String variant) {
|
||
|
StringBuilder buf = new StringBuilder();
|
||
|
|
||
|
if (lang != null && lang.length() > 0) {
|
||
|
buf.append(lang);
|
||
|
}
|
||
|
if (script != null && script.length() > 0) {
|
||
|
buf.append(UNDERSCORE);
|
||
|
buf.append(script);
|
||
|
}
|
||
|
if (country != null && country.length() > 0) {
|
||
|
buf.append(UNDERSCORE);
|
||
|
buf.append(country);
|
||
|
}
|
||
|
if (variant != null && variant.length() > 0) {
|
||
|
if (country == null || country.length() == 0) {
|
||
|
buf.append(UNDERSCORE);
|
||
|
}
|
||
|
buf.append(UNDERSCORE);
|
||
|
buf.append(variant);
|
||
|
}
|
||
|
return buf.toString();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Converts this ULocale object to a {@link java.util.Locale}.
|
||
|
* @return a {@link java.util.Locale} that either exactly represents this object
|
||
|
* or is the closest approximation.
|
||
|
*/
|
||
|
public Locale toLocale() {
|
||
|
if (locale == null) {
|
||
|
locale = JDKLocaleHelper.toLocale(this);
|
||
|
}
|
||
|
return locale;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Keep our own default ULocale.
|
||
|
*/
|
||
|
private static volatile ULocale defaultULocale;
|
||
|
|
||
|
private static Locale[] defaultCategoryLocales = new Locale[Category.values().length];
|
||
|
private static ULocale[] defaultCategoryULocales = new ULocale[Category.values().length];
|
||
|
|
||
|
static {
|
||
|
Locale defaultLocale = Locale.getDefault();
|
||
|
defaultULocale = forLocale(defaultLocale);
|
||
|
|
||
|
if (JDKLocaleHelper.hasLocaleCategories()) {
|
||
|
for (Category cat: Category.values()) {
|
||
|
int idx = cat.ordinal();
|
||
|
defaultCategoryLocales[idx] = JDKLocaleHelper.getDefault(cat);
|
||
|
defaultCategoryULocales[idx] = forLocale(defaultCategoryLocales[idx]);
|
||
|
}
|
||
|
} else {
|
||
|
// Android API level 21..23 does not have separate category locales,
|
||
|
// use the non-category default for all.
|
||
|
for (Category cat: Category.values()) {
|
||
|
int idx = cat.ordinal();
|
||
|
defaultCategoryLocales[idx] = defaultLocale;
|
||
|
defaultCategoryULocales[idx] = defaultULocale;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the current default ULocale.
|
||
|
* <p>
|
||
|
* The default ULocale is synchronized to the default Java Locale. This method checks
|
||
|
* the current default Java Locale and returns an equivalent ULocale.
|
||
|
*
|
||
|
* @return the default ULocale.
|
||
|
*/
|
||
|
public static ULocale getDefault() {
|
||
|
// Only synchronize if we must update the default locale.
|
||
|
ULocale currentDefaultULocale = defaultULocale;
|
||
|
if (currentDefaultULocale == null) {
|
||
|
// When Java's default locale has extensions (such as ja-JP-u-ca-japanese),
|
||
|
// Locale -> ULocale mapping requires BCP47 keyword mapping data that is currently
|
||
|
// stored in a resource bundle.
|
||
|
// If this happens during the class initialization's call to .forLocale(defaultLocale),
|
||
|
// then defaultULocale is still null until forLocale() returns.
|
||
|
// However, UResourceBundle currently requires non-null default ULocale.
|
||
|
// For now, this implementation returns ULocale.ROOT to avoid the problem.
|
||
|
// TODO: Consider moving BCP47 mapping data out of resource bundle later.
|
||
|
return ULocale.ROOT;
|
||
|
} else if (currentDefaultULocale.locale.equals(Locale.getDefault())) {
|
||
|
return currentDefaultULocale;
|
||
|
}
|
||
|
synchronized (ULocale.class) {
|
||
|
Locale currentDefault = Locale.getDefault();
|
||
|
assert currentDefault != null;
|
||
|
|
||
|
currentDefaultULocale = defaultULocale;
|
||
|
assert currentDefaultULocale != null;
|
||
|
|
||
|
if (currentDefaultULocale.locale.equals(currentDefault)) {
|
||
|
return currentDefaultULocale;
|
||
|
}
|
||
|
|
||
|
ULocale nextULocale = forLocale(currentDefault);
|
||
|
assert nextULocale != null;
|
||
|
|
||
|
if (!JDKLocaleHelper.hasLocaleCategories()) {
|
||
|
// Detected Java default Locale change.
|
||
|
// We need to update category defaults to match
|
||
|
// Java 7's behavior on Android API level 21..23.
|
||
|
for (Category cat : Category.values()) {
|
||
|
int idx = cat.ordinal();
|
||
|
defaultCategoryLocales[idx] = currentDefault;
|
||
|
defaultCategoryULocales[idx] = nextULocale;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return defaultULocale = nextULocale;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the default ULocale. This also sets the default Locale.
|
||
|
* If the caller does not have write permission to the
|
||
|
* user.language property, a security exception will be thrown,
|
||
|
* and the default ULocale will remain unchanged.
|
||
|
* <p>
|
||
|
* By setting the default ULocale with this method, all of the default category locales
|
||
|
* are also set to the specified default ULocale.
|
||
|
* @param newLocale the new default locale
|
||
|
* @throws SecurityException if a security manager exists and its
|
||
|
* <code>checkPermission</code> method doesn't allow the operation.
|
||
|
* @throws NullPointerException if <code>newLocale</code> is null
|
||
|
* @see SecurityManager#checkPermission(java.security.Permission)
|
||
|
* @see java.util.PropertyPermission
|
||
|
* @see ULocale#setDefault(Category, ULocale)
|
||
|
* @hide unsupported on Android
|
||
|
*/
|
||
|
public static synchronized void setDefault(ULocale newLocale){
|
||
|
Locale.setDefault(newLocale.toLocale());
|
||
|
defaultULocale = newLocale;
|
||
|
// This method also updates all category default locales
|
||
|
for (Category cat : Category.values()) {
|
||
|
setDefault(cat, newLocale);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the current default ULocale for the specified category.
|
||
|
*
|
||
|
* @param category the category
|
||
|
* @return the default ULocale for the specified category.
|
||
|
*/
|
||
|
public static ULocale getDefault(Category category) {
|
||
|
synchronized (ULocale.class) {
|
||
|
int idx = category.ordinal();
|
||
|
if (defaultCategoryULocales[idx] == null) {
|
||
|
// Just in case this method is called during ULocale class
|
||
|
// initialization. Unlike getDefault(), we do not have
|
||
|
// cyclic dependency for category default.
|
||
|
return ULocale.ROOT;
|
||
|
}
|
||
|
if (JDKLocaleHelper.hasLocaleCategories()) {
|
||
|
Locale currentCategoryDefault = JDKLocaleHelper.getDefault(category);
|
||
|
if (!defaultCategoryLocales[idx].equals(currentCategoryDefault)) {
|
||
|
defaultCategoryLocales[idx] = currentCategoryDefault;
|
||
|
defaultCategoryULocales[idx] = forLocale(currentCategoryDefault);
|
||
|
}
|
||
|
} else {
|
||
|
// java.util.Locale.setDefault(Locale) in Java 7 updates
|
||
|
// category locale defaults. On Android API level 21..23
|
||
|
// ICU4J checks if the default locale has changed and update
|
||
|
// category ULocales here if necessary.
|
||
|
|
||
|
// Note: When java.util.Locale.setDefault(Locale) is called
|
||
|
// with a Locale same with the previous one, Java 7 still
|
||
|
// updates category locale defaults. On Android API level 21..23
|
||
|
// there is no good way to detect the event, ICU4J simply
|
||
|
// checks if the default Java Locale has changed since last
|
||
|
// time.
|
||
|
|
||
|
Locale currentDefault = Locale.getDefault();
|
||
|
if (!defaultULocale.locale.equals(currentDefault)) {
|
||
|
defaultULocale = forLocale(currentDefault);
|
||
|
|
||
|
for (Category cat : Category.values()) {
|
||
|
int tmpIdx = cat.ordinal();
|
||
|
defaultCategoryLocales[tmpIdx] = currentDefault;
|
||
|
defaultCategoryULocales[tmpIdx] = forLocale(currentDefault);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// No synchronization with JDK Locale, because category default
|
||
|
// is not supported in Android API level 21..23.
|
||
|
}
|
||
|
return defaultCategoryULocales[idx];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the default <code>ULocale</code> for the specified <code>Category</code>.
|
||
|
* This also sets the default <code>Locale</code> for the specified <code>Category</code>
|
||
|
* of the JVM. If the caller does not have write permission to the
|
||
|
* user.language property, a security exception will be thrown,
|
||
|
* and the default ULocale for the specified Category will remain unchanged.
|
||
|
*
|
||
|
* @param category the specified category to set the default locale
|
||
|
* @param newLocale the new default locale
|
||
|
* @see SecurityManager#checkPermission(java.security.Permission)
|
||
|
* @see java.util.PropertyPermission
|
||
|
* @hide unsupported on Android
|
||
|
*/
|
||
|
public static synchronized void setDefault(Category category, ULocale newLocale) {
|
||
|
Locale newJavaDefault = newLocale.toLocale();
|
||
|
int idx = category.ordinal();
|
||
|
defaultCategoryULocales[idx] = newLocale;
|
||
|
defaultCategoryLocales[idx] = newJavaDefault;
|
||
|
JDKLocaleHelper.setDefault(category, newJavaDefault);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This is for compatibility with Locale-- in actuality, since ULocale is
|
||
|
* immutable, there is no reason to clone it, so this API returns 'this'.
|
||
|
*/
|
||
|
@Override
|
||
|
public Object clone() {
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the hashCode.
|
||
|
* @return a hash code value for this object.
|
||
|
*/
|
||
|
@Override
|
||
|
public int hashCode() {
|
||
|
return localeID.hashCode();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns true if the other object is another ULocale with the
|
||
|
* same full name.
|
||
|
* Note that since names are not canonicalized, two ULocales that
|
||
|
* function identically might not compare equal.
|
||
|
*
|
||
|
* @return true if this Locale is equal to the specified object.
|
||
|
*/
|
||
|
@Override
|
||
|
public boolean equals(Object obj) {
|
||
|
if (this == obj) {
|
||
|
return true;
|
||
|
}
|
||
|
if (obj instanceof ULocale) {
|
||
|
return localeID.equals(((ULocale)obj).localeID);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compares two ULocale for ordering.
|
||
|
* <p><b>Note:</b> The order might change in future.
|
||
|
*
|
||
|
* @param other the ULocale to be compared.
|
||
|
* @return a negative integer, zero, or a positive integer as this ULocale is less than, equal to, or greater
|
||
|
* than the specified ULocale.
|
||
|
* @throws NullPointerException if <code>other</code> is null.
|
||
|
*/
|
||
|
@Override
|
||
|
public int compareTo(ULocale other) {
|
||
|
if (this == other) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int cmp = 0;
|
||
|
|
||
|
// Language
|
||
|
cmp = getLanguage().compareTo(other.getLanguage());
|
||
|
if (cmp == 0) {
|
||
|
// Script
|
||
|
cmp = getScript().compareTo(other.getScript());
|
||
|
if (cmp == 0) {
|
||
|
// Region
|
||
|
cmp = getCountry().compareTo(other.getCountry());
|
||
|
if (cmp == 0) {
|
||
|
// Variant
|
||
|
cmp = getVariant().compareTo(other.getVariant());
|
||
|
if (cmp == 0) {
|
||
|
// Keywords
|
||
|
Iterator<String> thisKwdItr = getKeywords();
|
||
|
Iterator<String> otherKwdItr = other.getKeywords();
|
||
|
|
||
|
if (thisKwdItr == null) {
|
||
|
cmp = otherKwdItr == null ? 0 : -1;
|
||
|
} else if (otherKwdItr == null) {
|
||
|
cmp = 1;
|
||
|
} else {
|
||
|
// Both have keywords
|
||
|
while (cmp == 0 && thisKwdItr.hasNext()) {
|
||
|
if (!otherKwdItr.hasNext()) {
|
||
|
cmp = 1;
|
||
|
break;
|
||
|
}
|
||
|
// Compare keyword keys
|
||
|
String thisKey = thisKwdItr.next();
|
||
|
String otherKey = otherKwdItr.next();
|
||
|
cmp = thisKey.compareTo(otherKey);
|
||
|
if (cmp == 0) {
|
||
|
// Compare keyword values
|
||
|
String thisVal = getKeywordValue(thisKey);
|
||
|
String otherVal = other.getKeywordValue(otherKey);
|
||
|
if (thisVal == null) {
|
||
|
cmp = otherVal == null ? 0 : -1;
|
||
|
} else if (otherVal == null) {
|
||
|
cmp = 1;
|
||
|
} else {
|
||
|
cmp = thisVal.compareTo(otherVal);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (cmp == 0 && otherKwdItr.hasNext()) {
|
||
|
cmp = -1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Normalize the result value:
|
||
|
// Note: String.compareTo() may return value other than -1, 0, 1.
|
||
|
// A value other than those are OK by the definition, but we don't want
|
||
|
// associate any semantics other than negative/zero/positive.
|
||
|
return (cmp < 0) ? -1 : ((cmp > 0) ? 1 : 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu] Note:</strong> Unlike the Locale API, this returns an array of <code>ULocale</code>,
|
||
|
* not <code>Locale</code>.
|
||
|
*
|
||
|
* <p>Returns a list of all installed locales. This is equivalent to calling
|
||
|
* {@link #getAvailableLocalesByType} with AvailableType.DEFAULT.
|
||
|
*/
|
||
|
public static ULocale[] getAvailableLocales() {
|
||
|
return ICUResourceBundle.getAvailableULocales().clone();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a list of all installed locales according to the specified type.
|
||
|
*/
|
||
|
public static Collection<ULocale> getAvailableLocalesByType(AvailableType type) {
|
||
|
if (type == null) {
|
||
|
throw new IllegalArgumentException();
|
||
|
}
|
||
|
List<ULocale> result;
|
||
|
if (type == ULocale.AvailableType.WITH_LEGACY_ALIASES) {
|
||
|
result = new ArrayList<>();
|
||
|
Collections.addAll(result,
|
||
|
ICUResourceBundle.getAvailableULocales(ULocale.AvailableType.DEFAULT));
|
||
|
Collections.addAll(result,
|
||
|
ICUResourceBundle.getAvailableULocales(ULocale.AvailableType.ONLY_LEGACY_ALIASES));
|
||
|
} else {
|
||
|
result = Arrays.asList(ICUResourceBundle.getAvailableULocales(type));
|
||
|
}
|
||
|
return Collections.unmodifiableList(result);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a list of all 2-letter country codes defined in ISO 3166.
|
||
|
* Can be used to create Locales.
|
||
|
*/
|
||
|
public static String[] getISOCountries() {
|
||
|
return LocaleIDs.getISOCountries();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a list of all unique language codes defined in ISO 639.
|
||
|
* They can be 2 or 3 letter codes, as defined by
|
||
|
* <a href="https://www.ietf.org/rfc/bcp/bcp47.html#section-2.2.1">
|
||
|
* BCP 47, section 2.2.1</a>. Can be used to create Locales.
|
||
|
* [NOTE: ISO 639 is not a stable standard-- some languages' codes have changed.
|
||
|
* The list this function returns includes both the new and the old codes for the
|
||
|
* languages whose codes have changed.]
|
||
|
*/
|
||
|
public static String[] getISOLanguages() {
|
||
|
return LocaleIDs.getISOLanguages();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the language code for this locale, which will either be the empty string
|
||
|
* or a lowercase ISO 639 code.
|
||
|
* @see #getDisplayLanguage()
|
||
|
* @see #getDisplayLanguage(ULocale)
|
||
|
*/
|
||
|
public String getLanguage() {
|
||
|
return base().getLanguage();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the language code for the locale ID,
|
||
|
* which will either be the empty string
|
||
|
* or a lowercase ISO 639 code.
|
||
|
* @see #getDisplayLanguage()
|
||
|
* @see #getDisplayLanguage(ULocale)
|
||
|
*/
|
||
|
public static String getLanguage(String localeID) {
|
||
|
return new LocaleIDParser(localeID).getLanguage();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the script code for this locale, which might be the empty string.
|
||
|
* @see #getDisplayScript()
|
||
|
* @see #getDisplayScript(ULocale)
|
||
|
*/
|
||
|
public String getScript() {
|
||
|
return base().getScript();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the script code for the specified locale, which might be the empty
|
||
|
* string.
|
||
|
* @see #getDisplayScript()
|
||
|
* @see #getDisplayScript(ULocale)
|
||
|
*/
|
||
|
public static String getScript(String localeID) {
|
||
|
return new LocaleIDParser(localeID).getScript();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the country/region code for this locale, which will either be the empty string
|
||
|
* or an uppercase ISO 3166 2-letter code.
|
||
|
* @see #getDisplayCountry()
|
||
|
* @see #getDisplayCountry(ULocale)
|
||
|
*/
|
||
|
public String getCountry() {
|
||
|
return base().getRegion();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the country/region code for this locale, which will either be the empty string
|
||
|
* or an uppercase ISO 3166 2-letter code.
|
||
|
* @param localeID The locale identification string.
|
||
|
* @see #getDisplayCountry()
|
||
|
* @see #getDisplayCountry(ULocale)
|
||
|
*/
|
||
|
public static String getCountry(String localeID) {
|
||
|
return new LocaleIDParser(localeID).getCountry();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get region code from a key in locale or null.
|
||
|
*/
|
||
|
private static String getRegionFromKey(ULocale locale, String key) {
|
||
|
String region = locale.getKeywordValue(key);
|
||
|
if (region != null && region.length() >= 3 && region.length() <= 7) {
|
||
|
if (Character.isLetter(region.charAt(0))) {
|
||
|
return AsciiUtil.toUpperString(region.substring(0, 2));
|
||
|
} else {
|
||
|
// assume three-digit region code
|
||
|
return region.substring(0, 3);
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Get the region to use for supplemental data lookup.
|
||
|
* Uses
|
||
|
* (1) any region specified by locale tag "rg"; if none then
|
||
|
* (2) any unicode_region_tag in the locale ID; if none then
|
||
|
* (3) if inferRegion is true, the region suggested by
|
||
|
* getLikelySubtags on the localeID.
|
||
|
* If no region is found, returns empty string ""
|
||
|
*
|
||
|
* @param locale
|
||
|
* The locale (includes any keywords) from which
|
||
|
* to get the region to use for supplemental data.
|
||
|
* @param inferRegion
|
||
|
* If true, will try to infer region from other
|
||
|
* locale elements if not found any other way.
|
||
|
* @return
|
||
|
* String with region to use ("" if none found).
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public static String getRegionForSupplementalData(
|
||
|
ULocale locale, boolean inferRegion) {
|
||
|
String region = getRegionFromKey(locale, "rg");
|
||
|
if (region != null) {
|
||
|
return region;
|
||
|
}
|
||
|
region = locale.getCountry();
|
||
|
if (region.length() == 0 && inferRegion) {
|
||
|
region = getRegionFromKey(locale, "sd");
|
||
|
if (region != null) {
|
||
|
return region;
|
||
|
}
|
||
|
ULocale maximized = addLikelySubtags(locale);
|
||
|
region = maximized.getCountry();
|
||
|
}
|
||
|
return region;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the variant code for this locale, which might be the empty string.
|
||
|
* @see #getDisplayVariant()
|
||
|
* @see #getDisplayVariant(ULocale)
|
||
|
*/
|
||
|
public String getVariant() {
|
||
|
return base().getVariant();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the variant code for the specified locale, which might be the empty string.
|
||
|
* @see #getDisplayVariant()
|
||
|
* @see #getDisplayVariant(ULocale)
|
||
|
*/
|
||
|
public static String getVariant(String localeID) {
|
||
|
return new LocaleIDParser(localeID).getVariant();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the fallback locale for the specified locale, which might be the
|
||
|
* empty string.
|
||
|
*/
|
||
|
public static String getFallback(String localeID) {
|
||
|
return getFallbackString(getName(localeID));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the fallback locale for this locale. If this locale is root,
|
||
|
* returns null.
|
||
|
*/
|
||
|
public ULocale getFallback() {
|
||
|
if (localeID.length() == 0 || localeID.charAt(0) == '@') {
|
||
|
return null;
|
||
|
}
|
||
|
return new ULocale(getFallbackString(localeID), (Locale)null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the given (canonical) locale id minus the last part before the tags.
|
||
|
*/
|
||
|
private static String getFallbackString(String fallback) {
|
||
|
int extStart = fallback.indexOf('@');
|
||
|
if (extStart == -1) {
|
||
|
extStart = fallback.length();
|
||
|
}
|
||
|
int last = fallback.lastIndexOf('_', extStart);
|
||
|
if (last == -1) {
|
||
|
last = 0;
|
||
|
} else {
|
||
|
// truncate empty segment
|
||
|
while (last > 0) {
|
||
|
if (fallback.charAt(last - 1) != '_') {
|
||
|
break;
|
||
|
}
|
||
|
last--;
|
||
|
}
|
||
|
}
|
||
|
return fallback.substring(0, last) + fallback.substring(extStart);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the (normalized) base name for this locale,
|
||
|
* like {@link #getName()}, but without keywords.
|
||
|
*
|
||
|
* @return the base name as a String.
|
||
|
*/
|
||
|
public String getBaseName() {
|
||
|
return getBaseName(localeID);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the (normalized) base name for the specified locale,
|
||
|
* like {@link #getName(String)}, but without keywords.
|
||
|
*
|
||
|
* @param localeID the locale ID as a string
|
||
|
* @return the base name as a String.
|
||
|
*/
|
||
|
public static String getBaseName(String localeID){
|
||
|
if (localeID.indexOf('@') == -1) {
|
||
|
return localeID;
|
||
|
}
|
||
|
return new LocaleIDParser(localeID).getBaseName();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the (normalized) full name for this locale.
|
||
|
*
|
||
|
* @return String the full name of the localeID
|
||
|
*/
|
||
|
public String getName() {
|
||
|
return localeID; // always normalized
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the shortest length subtag's size.
|
||
|
*
|
||
|
* @param localeID
|
||
|
* @return The size of the shortest length subtag
|
||
|
**/
|
||
|
private static int getShortestSubtagLength(String localeID) {
|
||
|
int localeIDLength = localeID.length();
|
||
|
int length = localeIDLength;
|
||
|
boolean reset = true;
|
||
|
int tmpLength = 0;
|
||
|
|
||
|
for (int i = 0; i < localeIDLength; i++) {
|
||
|
if (localeID.charAt(i) != '_' && localeID.charAt(i) != '-') {
|
||
|
if (reset) {
|
||
|
reset = false;
|
||
|
tmpLength = 0;
|
||
|
}
|
||
|
tmpLength++;
|
||
|
} else {
|
||
|
if (tmpLength != 0 && tmpLength < length) {
|
||
|
length = tmpLength;
|
||
|
}
|
||
|
reset = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return length;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the (normalized) full name for the specified locale.
|
||
|
*
|
||
|
* @param localeID the localeID as a string
|
||
|
* @return String the full name of the localeID
|
||
|
*/
|
||
|
public static String getName(String localeID){
|
||
|
String tmpLocaleID = localeID;
|
||
|
// Convert BCP47 id if necessary
|
||
|
if (localeID != null && !localeID.contains("@") && getShortestSubtagLength(localeID) == 1) {
|
||
|
if (localeID.indexOf('_') >= 0 && localeID.charAt(1) != '_' && localeID.charAt(1) != '-') {
|
||
|
tmpLocaleID = localeID.replace('_', '-');
|
||
|
}
|
||
|
tmpLocaleID = forLanguageTag(tmpLocaleID).getName();
|
||
|
if (tmpLocaleID.length() == 0) {
|
||
|
tmpLocaleID = localeID;
|
||
|
}
|
||
|
} else if ("root".equalsIgnoreCase(localeID)) {
|
||
|
tmpLocaleID = EMPTY_STRING;
|
||
|
} else {
|
||
|
tmpLocaleID = stripLeadingUnd(localeID);
|
||
|
}
|
||
|
return nameCache.getInstance(tmpLocaleID, null /* unused */);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Strips out the leading "und" language code case-insensitively.
|
||
|
*
|
||
|
* @implNote Avoids creating new local non-primitive objects to reduce GC pressure.
|
||
|
*/
|
||
|
private static String stripLeadingUnd(String localeID) {
|
||
|
int length = localeID.length();
|
||
|
if (length < 3) {
|
||
|
return localeID;
|
||
|
}
|
||
|
|
||
|
// If not starts with "und", return.
|
||
|
if (!localeID.regionMatches(/*ignoreCase=*/true, 0, "und", 0, /*len=*/3)) {
|
||
|
return localeID;
|
||
|
}
|
||
|
|
||
|
// The string is equals to "und" case-insensitively.
|
||
|
if (length == 3) {
|
||
|
return EMPTY_STRING;
|
||
|
}
|
||
|
|
||
|
// localeID must have a length >= 4
|
||
|
char separator = localeID.charAt(3);
|
||
|
if (separator == '-' || separator == '_') { // "und-*" or "und_*"
|
||
|
return localeID.substring(3);
|
||
|
}
|
||
|
|
||
|
return localeID;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a string representation of this object.
|
||
|
* @return a string representation of the object.
|
||
|
*/
|
||
|
@Override
|
||
|
public String toString() {
|
||
|
return localeID;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns an iterator over keywords for this locale. If there
|
||
|
* are no keywords, returns null.
|
||
|
* @return iterator over keywords, or null if there are no keywords.
|
||
|
*/
|
||
|
public Iterator<String> getKeywords() {
|
||
|
return getKeywords(localeID);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns an iterator over keywords for the specified locale. If there
|
||
|
* are no keywords, returns null.
|
||
|
* @return an iterator over the keywords in the specified locale, or null
|
||
|
* if there are no keywords.
|
||
|
*/
|
||
|
public static Iterator<String> getKeywords(String localeID){
|
||
|
return new LocaleIDParser(localeID).getKeywords();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the value for a keyword in this locale. If the keyword is not
|
||
|
* defined, returns null.
|
||
|
* @param keywordName name of the keyword whose value is desired. Case insensitive.
|
||
|
* @return the value of the keyword, or null.
|
||
|
*/
|
||
|
public String getKeywordValue(String keywordName){
|
||
|
return getKeywordValue(localeID, keywordName);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the value for a keyword in the specified locale. If the keyword is
|
||
|
* not defined, returns null. The locale name does not need to be normalized.
|
||
|
* @param keywordName name of the keyword whose value is desired. Case insensitive.
|
||
|
* @return String the value of the keyword as a string
|
||
|
*/
|
||
|
public static String getKeywordValue(String localeID, String keywordName) {
|
||
|
return new LocaleIDParser(localeID).getKeywordValue(keywordName);
|
||
|
}
|
||
|
|
||
|
static private class AliasReplacer {
|
||
|
/**
|
||
|
* @param language language subtag to be replaced. Cannot be null but could be empty.
|
||
|
* @param script script subtag to be replaced. Cannot be null but could be empty.
|
||
|
* @param region region subtag to be replaced. Cannot be null but could be empty.
|
||
|
* @param variants variant subtags to be replaced. Cannot be null but could be empty.
|
||
|
* @param extensions extensions in string to be replaced. Cannot be null but could be empty.
|
||
|
*/
|
||
|
public AliasReplacer(String language, String script, String region,
|
||
|
String variants, String extensions) {
|
||
|
|
||
|
assert language != null;
|
||
|
assert script != null;
|
||
|
assert region != null;
|
||
|
assert variants != null;
|
||
|
assert extensions != null;
|
||
|
this.language = language;
|
||
|
this.script = script;
|
||
|
this.region = region;
|
||
|
if (!variants.isEmpty()) {
|
||
|
this.variants =
|
||
|
new ArrayList<>(Arrays.asList(variants.split("_")));
|
||
|
}
|
||
|
this.extensions = extensions;
|
||
|
}
|
||
|
|
||
|
private String language;
|
||
|
private String script;
|
||
|
private String region;
|
||
|
private List<String> variants;
|
||
|
private String extensions;
|
||
|
|
||
|
public String replace() {
|
||
|
boolean changed = false;
|
||
|
loadAliasData();
|
||
|
int count = 0;
|
||
|
while (true) {
|
||
|
if (count++ > 10) {
|
||
|
// Throw exception when we loop through too many time
|
||
|
// stop to avoid infinity loop cauesd by incorrect data
|
||
|
// in resource.
|
||
|
throw new IllegalArgumentException(
|
||
|
"Have problem to resolve locale alias of " +
|
||
|
lscvToID(language, script, region,
|
||
|
((variants == null) ? "" : Utility.joinStrings("_", variants))) +
|
||
|
extensions);
|
||
|
}
|
||
|
// Anytime we replace something, we need to start over again.
|
||
|
// lang REGION variant
|
||
|
if ( replaceLanguage(true, true, true) ||
|
||
|
replaceLanguage(true, true, false) ||
|
||
|
replaceLanguage(true, false, true) ||
|
||
|
replaceLanguage(true, false, false) ||
|
||
|
replaceLanguage(false, false, true) ||
|
||
|
replaceRegion() ||
|
||
|
replaceScript() ||
|
||
|
replaceVariant()) {
|
||
|
// Some values in data is changed, try to match from the
|
||
|
// beginning again.
|
||
|
changed = true;
|
||
|
continue;
|
||
|
}
|
||
|
// Nothing changed in this iteration, break out the loop
|
||
|
break;
|
||
|
} // while(1)
|
||
|
if (extensions == null && !changed) {
|
||
|
return null;
|
||
|
}
|
||
|
String result = lscvToID(language, script, region,
|
||
|
((variants == null) ? "" : Utility.joinStrings("_", variants)));
|
||
|
if (extensions != null) {
|
||
|
boolean keywordChanged = false;
|
||
|
ULocale temp = new ULocale(result + extensions);
|
||
|
Iterator<String> keywords = temp.getKeywords();
|
||
|
while (keywords != null && keywords.hasNext()) {
|
||
|
String key = keywords.next();
|
||
|
if (key.equals("rg") || key.equals("sd") || key.equals("t")) {
|
||
|
String value = temp.getKeywordValue(key);
|
||
|
String replacement = key.equals("t") ?
|
||
|
replaceTransformedExtensions(value) :
|
||
|
replaceSubdivision(value);
|
||
|
if (replacement != null) {
|
||
|
temp = temp.setKeywordValue(key, replacement);
|
||
|
keywordChanged = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (keywordChanged) {
|
||
|
extensions = temp.getName().substring(temp.getBaseName().length());
|
||
|
changed = true;
|
||
|
}
|
||
|
result += extensions;
|
||
|
}
|
||
|
if (changed) {
|
||
|
return result;
|
||
|
}
|
||
|
// Nothing changed in any iteration of the loop.
|
||
|
return null;
|
||
|
};
|
||
|
|
||
|
private static boolean aliasDataIsLoaded = false;
|
||
|
private static Map<String, String> languageAliasMap = null;
|
||
|
private static Map<String, String> scriptAliasMap = null;
|
||
|
private static Map<String, List<String>> territoryAliasMap = null;
|
||
|
private static Map<String, String> variantAliasMap = null;
|
||
|
private static Map<String, String> subdivisionAliasMap = null;
|
||
|
|
||
|
/*
|
||
|
* Initializes the alias data from the ICU resource bundles. The alias
|
||
|
* data contains alias of language, country, script and variants.
|
||
|
*
|
||
|
* If the alias data has already loaded, then this method simply
|
||
|
* returns without doing anything meaningful.
|
||
|
*
|
||
|
*/
|
||
|
private static synchronized void loadAliasData() {
|
||
|
if (aliasDataIsLoaded) {
|
||
|
return;
|
||
|
}
|
||
|
languageAliasMap = new HashMap<>();
|
||
|
scriptAliasMap = new HashMap<>();
|
||
|
territoryAliasMap = new HashMap<>();
|
||
|
variantAliasMap = new HashMap<>();
|
||
|
subdivisionAliasMap = new HashMap<>();
|
||
|
|
||
|
UResourceBundle metadata = UResourceBundle.getBundleInstance(
|
||
|
ICUData.ICU_BASE_NAME, "metadata",
|
||
|
ICUResourceBundle.ICU_DATA_CLASS_LOADER);
|
||
|
UResourceBundle metadataAlias = metadata.get("alias");
|
||
|
UResourceBundle languageAlias = metadataAlias.get("language");
|
||
|
UResourceBundle scriptAlias = metadataAlias.get("script");
|
||
|
UResourceBundle territoryAlias = metadataAlias.get("territory");
|
||
|
UResourceBundle variantAlias = metadataAlias.get("variant");
|
||
|
UResourceBundle subdivisionAlias = metadataAlias.get("subdivision");
|
||
|
|
||
|
for (int i = 0 ; i < languageAlias.getSize(); i++) {
|
||
|
UResourceBundle res = languageAlias.get(i);
|
||
|
String aliasFrom = res.getKey();
|
||
|
String aliasTo = res.get("replacement").getString();
|
||
|
Locale testLocale = new Locale(aliasFrom);
|
||
|
// if there are script in the aliasFrom
|
||
|
// or we have both a und as language and a region code.
|
||
|
if ( ! testLocale.getScript().isEmpty() ||
|
||
|
(aliasFrom.startsWith("und") && ! testLocale.getCountry().isEmpty())) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"key [" + aliasFrom +
|
||
|
"] in alias:language contains unsupported fields combination.");
|
||
|
}
|
||
|
languageAliasMap.put(aliasFrom, aliasTo);
|
||
|
}
|
||
|
for (int i = 0 ; i < scriptAlias.getSize(); i++) {
|
||
|
UResourceBundle res = scriptAlias.get(i);
|
||
|
String aliasFrom = res.getKey();
|
||
|
String aliasTo = res.get("replacement").getString();
|
||
|
if (aliasFrom.length() != 4) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"Incorrect key [" + aliasFrom + "] in alias:script.");
|
||
|
}
|
||
|
scriptAliasMap.put(aliasFrom, aliasTo);
|
||
|
}
|
||
|
for (int i = 0 ; i < territoryAlias.getSize(); i++) {
|
||
|
UResourceBundle res = territoryAlias.get(i);
|
||
|
String aliasFrom = res.getKey();
|
||
|
String aliasTo = res.get("replacement").getString();
|
||
|
if (aliasFrom.length() < 2 || aliasFrom.length() > 3) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"Incorrect key [" + aliasFrom + "] in alias:territory.");
|
||
|
}
|
||
|
territoryAliasMap.put(aliasFrom,
|
||
|
new ArrayList<>(Arrays.asList(aliasTo.split(" "))));
|
||
|
}
|
||
|
for (int i = 0 ; i < variantAlias.getSize(); i++) {
|
||
|
UResourceBundle res = variantAlias.get(i);
|
||
|
String aliasFrom = res.getKey();
|
||
|
String aliasTo = res.get("replacement").getString();
|
||
|
if ( aliasFrom.length() < 4 ||
|
||
|
aliasFrom.length() > 8 ||
|
||
|
(aliasFrom.length() == 4 &&
|
||
|
(aliasFrom.charAt(0) < '0' || aliasFrom.charAt(0) > '9'))) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"Incorrect key [" + aliasFrom + "] in alias:variant.");
|
||
|
}
|
||
|
if ( aliasTo.length() < 4 ||
|
||
|
aliasTo.length() > 8 ||
|
||
|
(aliasTo.length() == 4 &&
|
||
|
(aliasTo.charAt(0) < '0' || aliasTo.charAt(0) > '9'))) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"Incorrect variant [" + aliasTo + "] for the key [" + aliasFrom +
|
||
|
"] in alias:variant.");
|
||
|
}
|
||
|
variantAliasMap.put(aliasFrom, aliasTo);
|
||
|
}
|
||
|
for (int i = 0 ; i < subdivisionAlias.getSize(); i++) {
|
||
|
UResourceBundle res = subdivisionAlias.get(i);
|
||
|
String aliasFrom = res.getKey();
|
||
|
String aliasTo = res.get("replacement").getString().split(" ")[0];
|
||
|
if (aliasFrom.length() < 3 || aliasFrom.length() > 8) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"Incorrect key [" + aliasFrom + "] in alias:territory.");
|
||
|
}
|
||
|
if (aliasTo.length() == 2) {
|
||
|
// Add 'zzzz' based on changes to UTS #35 for CLDR-14312.
|
||
|
aliasTo += "zzzz";
|
||
|
} else if (aliasTo.length() < 2 || aliasTo.length() > 8) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"Incorrect value [" + aliasTo + "] in alias:territory.");
|
||
|
}
|
||
|
subdivisionAliasMap.put(aliasFrom, aliasTo);
|
||
|
}
|
||
|
|
||
|
aliasDataIsLoaded = true;
|
||
|
}
|
||
|
|
||
|
private static String generateKey(
|
||
|
String language, String region, String variant) {
|
||
|
assert variant == null || variant.length() >= 4;
|
||
|
StringBuilder buf = new StringBuilder();
|
||
|
buf.append(language);
|
||
|
if (region != null && !region.isEmpty()) {
|
||
|
buf.append(UNDERSCORE);
|
||
|
buf.append(region);
|
||
|
}
|
||
|
if (variant != null && !variant.isEmpty()) {
|
||
|
buf.append(UNDERSCORE);
|
||
|
buf.append(variant);
|
||
|
}
|
||
|
return buf.toString();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* If replacement is neither null nor empty and input is either null or empty,
|
||
|
* return replacement.
|
||
|
* If replacement is neither null nor empty but input is not empty, return input.
|
||
|
* If replacement is either null or empty and type is either null or empty,
|
||
|
* return input.
|
||
|
* Otherwise return null.
|
||
|
* replacement input type return
|
||
|
* AAA "" * AAA
|
||
|
* AAA BBB * BBB
|
||
|
* "" CCC "" CCC
|
||
|
* "" * i DDD ""
|
||
|
*/
|
||
|
private static String deleteOrReplace(
|
||
|
String input, String type, String replacement) {
|
||
|
return (replacement != null && !replacement.isEmpty()) ?
|
||
|
((input == null || input.isEmpty()) ? replacement : input) :
|
||
|
((type == null || type.isEmpty()) ? input : null);
|
||
|
}
|
||
|
|
||
|
private boolean replaceLanguage(boolean checkLanguage,
|
||
|
boolean checkRegion, boolean checkVariants) {
|
||
|
if ( (checkRegion && (region == null || region.isEmpty())) ||
|
||
|
(checkVariants && (variants == null))) {
|
||
|
// Nothing to search
|
||
|
return false;
|
||
|
}
|
||
|
int variantSize = checkVariants ? variants.size() : 1;
|
||
|
// Since we may have more than one variant, we need to loop through
|
||
|
// them.
|
||
|
String searchLanguage = checkLanguage ? language : UNDEFINED_LANGUAGE;
|
||
|
String searchRegion = checkRegion ? region : null;
|
||
|
String searchVariant = null;
|
||
|
for (int variantIndex = 0; variantIndex < variantSize; ++variantIndex) {
|
||
|
if (checkVariants) {
|
||
|
searchVariant = variants.get(variantIndex);
|
||
|
}
|
||
|
if (searchVariant != null && searchVariant.length() < 4) {
|
||
|
// Do not consider ill-formed variant subtag.
|
||
|
searchVariant = null;
|
||
|
}
|
||
|
String typeKey = generateKey(
|
||
|
searchLanguage, searchRegion, searchVariant);
|
||
|
String replacement = languageAliasMap.get(typeKey);
|
||
|
if (replacement == null) {
|
||
|
// Found no replacement data.
|
||
|
continue;
|
||
|
}
|
||
|
String replacedScript = null;
|
||
|
String replacedRegion = null;
|
||
|
String replacedVariant = null;
|
||
|
String replacedExtensions = null;
|
||
|
String replacedLanguage = null;
|
||
|
|
||
|
if (replacement.indexOf('_') < 0) {
|
||
|
replacedLanguage = replacement.equals(UNDEFINED_LANGUAGE) ?
|
||
|
language : replacement;
|
||
|
} else {
|
||
|
String[] replacementFields = replacement.split("_");
|
||
|
replacedLanguage = replacementFields[0];
|
||
|
int index = 1;
|
||
|
|
||
|
if (replacedLanguage.equals(UNDEFINED_LANGUAGE)) {
|
||
|
replacedLanguage = language;
|
||
|
}
|
||
|
int consumed = replacementFields[0].length() + 1;
|
||
|
while (replacementFields.length > index) {
|
||
|
String field = replacementFields[index];
|
||
|
int len = field.length();
|
||
|
if (1 == len) {
|
||
|
replacedExtensions = replacement.substring(consumed);
|
||
|
break;
|
||
|
} else if (len >= 2 && len <= 3) {
|
||
|
assert replacedRegion == null;
|
||
|
replacedRegion = field;
|
||
|
} else if (len >= 5 && len <= 8) {
|
||
|
assert replacedVariant == null;
|
||
|
replacedVariant = field;
|
||
|
} else if (len == 4) {
|
||
|
if (field.charAt(0) >= '0' && field.charAt(0) <= '9') {
|
||
|
assert replacedVariant == null;
|
||
|
replacedVariant = field;
|
||
|
} else {
|
||
|
assert replacedScript == null;
|
||
|
replacedScript = field;
|
||
|
}
|
||
|
}
|
||
|
index++;
|
||
|
consumed += len + 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
replacedScript = deleteOrReplace(script, null, replacedScript);
|
||
|
replacedRegion = deleteOrReplace(region, searchRegion, replacedRegion);
|
||
|
replacedVariant = deleteOrReplace(searchVariant, searchVariant, replacedVariant);
|
||
|
|
||
|
if ( this.language.equals(replacedLanguage) &&
|
||
|
this.script.equals(replacedScript) &&
|
||
|
this.region.equals(replacedRegion) &&
|
||
|
Objects.equals(searchVariant, replacedVariant) &&
|
||
|
replacedExtensions == null) {
|
||
|
// Replacement produce no changes on search.
|
||
|
// For example, apply pa_IN=> pa_Guru_IN on pa_Guru_IN.
|
||
|
continue;
|
||
|
}
|
||
|
this.language = replacedLanguage;
|
||
|
this.script = replacedScript;
|
||
|
this.region = replacedRegion;
|
||
|
if (searchVariant != null && !searchVariant.isEmpty()) {
|
||
|
if (replacedVariant != null && !replacedVariant.isEmpty()) {
|
||
|
this.variants.set(variantIndex, replacedVariant);
|
||
|
} else {
|
||
|
this.variants.remove(variantIndex);
|
||
|
if (this.variants.isEmpty()) {
|
||
|
this.variants = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (replacedExtensions != null && !replacedExtensions.isEmpty()) {
|
||
|
// DO NOTHING
|
||
|
// UTS35 does not specifiy what should we do if we have extensions in the
|
||
|
// replacement. Currently we know only the following 4 "BCP47 LegacyRules" have
|
||
|
// extensions in them languageAlias:
|
||
|
// i_default => en_x_i_default
|
||
|
// i_enochian => und_x_i_enochian
|
||
|
// i_mingo => see_x_i_mingo
|
||
|
// zh_min => nan_x_zh_min
|
||
|
// But all of them are already changed by code inside LanguageTag before
|
||
|
// hitting this code.
|
||
|
}
|
||
|
// Something in search changed by language alias data.
|
||
|
return true;
|
||
|
}
|
||
|
// Nothing changed in search by language alias data.
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private boolean replaceRegion() {
|
||
|
if (region == null || region.isEmpty()) return false;
|
||
|
List<String> replacement = territoryAliasMap.get(region);
|
||
|
if (replacement == null) {
|
||
|
// Found no replacement data for this region.
|
||
|
return false;
|
||
|
}
|
||
|
String replacedRegion;
|
||
|
if (replacement.size() > 1) {
|
||
|
String regionOfLanguageAndScript =
|
||
|
ULocale.addLikelySubtags(
|
||
|
new ULocale(this.language, this.script, null))
|
||
|
.getCountry();
|
||
|
replacedRegion = replacement.contains(regionOfLanguageAndScript) ?
|
||
|
regionOfLanguageAndScript : replacement.get(0);
|
||
|
} else {
|
||
|
replacedRegion = replacement.get(0);
|
||
|
}
|
||
|
assert !this.region.equals(replacedRegion);
|
||
|
this.region = replacedRegion;
|
||
|
// The region is changed by data in territory alias.
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private boolean replaceScript() {
|
||
|
if (script == null || script.isEmpty()) return false;
|
||
|
String replacement = scriptAliasMap.get(script);
|
||
|
if (replacement == null) {
|
||
|
// Found no replacement data for this script.
|
||
|
return false;
|
||
|
}
|
||
|
assert !this.script.equals(replacement);
|
||
|
this.script = replacement;
|
||
|
// The script is changed by data in script alias.
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private boolean replaceVariant() {
|
||
|
if (variants == null) return false;
|
||
|
for (int i = 0; i < variants.size(); i++) {
|
||
|
String variant = variants.get(i);
|
||
|
String replacement = variantAliasMap.get(variant);
|
||
|
if (replacement == null) {
|
||
|
// Found no replacement data for this variant.
|
||
|
continue;
|
||
|
}
|
||
|
assert replacement.length() >= 4;
|
||
|
assert replacement.length() <= 8;
|
||
|
assert replacement.length() != 4 ||
|
||
|
( replacement.charAt(0) >= '0' && replacement.charAt(0) <= '9');
|
||
|
if (!variant.equals(replacement)) {
|
||
|
variants.set(i, replacement);
|
||
|
// Special hack to handle hepburn-heploc => alalc97
|
||
|
if (variant.equals("heploc")) {
|
||
|
variants.remove("hepburn");
|
||
|
if (variants.isEmpty()) {
|
||
|
variants = null;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private String replaceSubdivision(String subdivision) {
|
||
|
return subdivisionAliasMap.get(subdivision);
|
||
|
}
|
||
|
|
||
|
private String replaceTransformedExtensions(String extensions) {
|
||
|
StringBuilder builder = new StringBuilder();
|
||
|
List<String> subtags = new ArrayList<>(Arrays.asList(extensions.split(LanguageTag.SEP)));
|
||
|
List<String> tfields = new ArrayList<>();
|
||
|
int processedLength = 0;
|
||
|
int tlangLength = 0;
|
||
|
String tkey = "";
|
||
|
for (String subtag : subtags) {
|
||
|
if (LanguageTag.isTKey(subtag)) {
|
||
|
if (tlangLength == 0) {
|
||
|
// Found the first tkey. Record the total length of the preceding
|
||
|
// tlang subtags. -1 if there is no tlang before the first tkey.
|
||
|
tlangLength = processedLength-1;
|
||
|
}
|
||
|
if (builder.length() > 0) {
|
||
|
// Finish & store the previous tkey with its tvalue subtags.
|
||
|
tfields.add(builder.toString());
|
||
|
builder.setLength(0);
|
||
|
}
|
||
|
// Start collecting subtags for this new tkey.
|
||
|
tkey = subtag;
|
||
|
builder.append(subtag);
|
||
|
} else {
|
||
|
if (tlangLength != 0) {
|
||
|
builder.append(LanguageTag.SEP).append(toUnicodeLocaleType(tkey, subtag));
|
||
|
}
|
||
|
}
|
||
|
processedLength += subtag.length() + 1;
|
||
|
}
|
||
|
if (builder.length() > 0) {
|
||
|
// Finish & store the previous=last tkey with its tvalue subtags.
|
||
|
tfields.add(builder.toString());
|
||
|
builder.setLength(0);
|
||
|
}
|
||
|
String tlang = (tlangLength > 0) ? extensions.substring(0, tlangLength) :
|
||
|
((tfields.size() == 0) ? extensions : "");
|
||
|
if (tlang.length() > 0) {
|
||
|
String canonicalized = ULocale.createCanonical(
|
||
|
ULocale.forLanguageTag(extensions)).toLanguageTag();
|
||
|
builder.append(AsciiUtil.toLowerString(canonicalized));
|
||
|
}
|
||
|
|
||
|
if (tfields.size() > 0) {
|
||
|
if (builder.length() > 0) {
|
||
|
builder.append(LanguageTag.SEP);
|
||
|
}
|
||
|
// tfields are sorted by alphabetical order of their keys
|
||
|
Collections.sort(tfields);
|
||
|
builder.append(Utility.joinStrings(LanguageTag.SEP, tfields));
|
||
|
}
|
||
|
return builder.toString();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the canonical name according to CLDR for the specified locale ID.
|
||
|
* This is used to convert POSIX and other legacy IDs to standard ICU form.
|
||
|
* @param localeID the locale id
|
||
|
* @return the canonicalized id
|
||
|
*/
|
||
|
public static String canonicalize(String localeID){
|
||
|
LocaleIDParser parser = new LocaleIDParser(localeID, true);
|
||
|
String baseName = parser.getBaseName();
|
||
|
boolean foundVariant = false;
|
||
|
|
||
|
if (localeID.equals("")) {
|
||
|
return "";
|
||
|
}
|
||
|
|
||
|
// we have an ID in the form xx_Yyyy_ZZ_KKKKK
|
||
|
|
||
|
/* See if this is an already known locale */
|
||
|
for (int i = 0; i < CANONICALIZE_MAP.length; i++) {
|
||
|
String[] vals = CANONICALIZE_MAP[i];
|
||
|
if (vals[0].equals(baseName)) {
|
||
|
foundVariant = true;
|
||
|
|
||
|
parser.setBaseName(vals[1]);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* total mondo hack for Norwegian, fortunately the main NY case is handled earlier */
|
||
|
if (!foundVariant) {
|
||
|
if (parser.getLanguage().equals("nb") && parser.getVariant().equals("NY")) {
|
||
|
parser.setBaseName(lscvToID("nn", parser.getScript(), parser.getCountry(), null));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
String name = parser.getName();
|
||
|
if (!isKnownCanonicalizedLocale(name)) {
|
||
|
AliasReplacer replacer = new AliasReplacer(
|
||
|
parser.getLanguage(), parser.getScript(), parser.getCountry(),
|
||
|
AsciiUtil.toLowerString(parser.getVariant()),
|
||
|
parser.getName().substring(parser.getBaseName().length()));
|
||
|
String replaced = replacer.replace();
|
||
|
if (replaced != null) {
|
||
|
parser = new LocaleIDParser(replaced);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return parser.getName();
|
||
|
}
|
||
|
|
||
|
private static synchronized boolean isKnownCanonicalizedLocale(String name) {
|
||
|
if (name.equals("c") || name.equals("en") || name.equals("en_US")) {
|
||
|
return true;
|
||
|
}
|
||
|
if (gKnownCanonicalizedCases == null) {
|
||
|
List<String> items = Arrays.asList(
|
||
|
"af", "af_ZA", "am", "am_ET", "ar", "ar_001", "as", "as_IN", "az", "az_AZ",
|
||
|
"be", "be_BY", "bg", "bg_BG", "bn", "bn_IN", "bs", "bs_BA", "ca", "ca_ES",
|
||
|
"cs", "cs_CZ", "cy", "cy_GB", "da", "da_DK", "de", "de_DE", "el", "el_GR",
|
||
|
"en", "en_GB", "en_US", "es", "es_419", "es_ES", "et", "et_EE", "eu",
|
||
|
"eu_ES", "fa", "fa_IR", "fi", "fi_FI", "fil", "fil_PH", "fr", "fr_FR",
|
||
|
"ga", "ga_IE", "gl", "gl_ES", "gu", "gu_IN", "he", "he_IL", "hi", "hi_IN",
|
||
|
"hr", "hr_HR", "hu", "hu_HU", "hy", "hy_AM", "id", "id_ID", "is", "is_IS",
|
||
|
"it", "it_IT", "ja", "ja_JP", "jv", "jv_ID", "ka", "ka_GE", "kk", "kk_KZ",
|
||
|
"km", "km_KH", "kn", "kn_IN", "ko", "ko_KR", "ky", "ky_KG", "lo", "lo_LA",
|
||
|
"lt", "lt_LT", "lv", "lv_LV", "mk", "mk_MK", "ml", "ml_IN", "mn", "mn_MN",
|
||
|
"mr", "mr_IN", "ms", "ms_MY", "my", "my_MM", "nb", "nb_NO", "ne", "ne_NP",
|
||
|
"nl", "nl_NL", "no", "or", "or_IN", "pa", "pa_IN", "pl", "pl_PL", "ps", "ps_AF",
|
||
|
"pt", "pt_BR", "pt_PT", "ro", "ro_RO", "ru", "ru_RU", "sd", "sd_IN", "si",
|
||
|
"si_LK", "sk", "sk_SK", "sl", "sl_SI", "so", "so_SO", "sq", "sq_AL", "sr",
|
||
|
"sr_Cyrl_RS", "sr_Latn", "sr_RS", "sv", "sv_SE", "sw", "sw_TZ", "ta",
|
||
|
"ta_IN", "te", "te_IN", "th", "th_TH", "tk", "tk_TM", "tr", "tr_TR", "uk",
|
||
|
"uk_UA", "ur", "ur_PK", "uz", "uz_UZ", "vi", "vi_VN", "yue", "yue_Hant",
|
||
|
"yue_Hant_HK", "yue_HK", "zh", "zh_CN", "zh_Hans", "zh_Hans_CN", "zh_Hant",
|
||
|
"zh_Hant_TW", "zh_TW", "zu", "zu_ZA");
|
||
|
gKnownCanonicalizedCases = new HashSet<>(items);
|
||
|
|
||
|
}
|
||
|
return gKnownCanonicalizedCases.contains(name);
|
||
|
}
|
||
|
|
||
|
private static Set<String> gKnownCanonicalizedCases = null;
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Given a keyword and a value, return a new locale with an updated
|
||
|
* keyword and value. If the keyword is null, this removes all keywords from the locale id.
|
||
|
* Otherwise, if the value is null, this removes the value for this keyword from the
|
||
|
* locale id. Otherwise, this adds/replaces the value for this keyword in the locale id.
|
||
|
* The keyword and value must not be empty.
|
||
|
*
|
||
|
* <p>Related: {@link #getBaseName()} returns the locale ID string with all keywords removed.
|
||
|
*
|
||
|
* @param keyword the keyword to add/remove, or null to remove all keywords.
|
||
|
* @param value the value to add/set, or null to remove this particular keyword.
|
||
|
* @return the updated locale
|
||
|
*/
|
||
|
public ULocale setKeywordValue(String keyword, String value) {
|
||
|
return new ULocale(setKeywordValue(localeID, keyword, value), (Locale)null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Given a locale id, a keyword, and a value, return a new locale id with an updated
|
||
|
* keyword and value. If the keyword is null, this removes all keywords from the locale id.
|
||
|
* Otherwise, if the value is null, this removes the value for this keyword from the
|
||
|
* locale id. Otherwise, this adds/replaces the value for this keyword in the locale id.
|
||
|
* The keyword and value must not be empty.
|
||
|
*
|
||
|
* <p>Related: {@link #getBaseName(String)} returns the locale ID string with all keywords removed.
|
||
|
*
|
||
|
* @param localeID the locale id to modify
|
||
|
* @param keyword the keyword to add/remove, or null to remove all keywords.
|
||
|
* @param value the value to add/set, or null to remove this particular keyword.
|
||
|
* @return the updated locale id
|
||
|
*/
|
||
|
public static String setKeywordValue(String localeID, String keyword, String value) {
|
||
|
LocaleIDParser parser = new LocaleIDParser(localeID);
|
||
|
parser.setKeywordValue(keyword, value);
|
||
|
return parser.getName();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Given a locale id, a keyword, and a value, return a new locale id with an updated
|
||
|
* keyword and value, if the keyword does not already have a value. The keyword and
|
||
|
* value must not be null or empty.
|
||
|
* @param localeID the locale id to modify
|
||
|
* @param keyword the keyword to add, if not already present
|
||
|
* @param value the value to add, if not already present
|
||
|
* @return the updated locale id
|
||
|
*/
|
||
|
/* private static String defaultKeywordValue(String localeID, String keyword, String value) {
|
||
|
LocaleIDParser parser = new LocaleIDParser(localeID);
|
||
|
parser.defaultKeywordValue(keyword, value);
|
||
|
return parser.getName();
|
||
|
}*/
|
||
|
|
||
|
/**
|
||
|
* Returns a three-letter abbreviation for this locale's language. If the locale
|
||
|
* doesn't specify a language, returns the empty string. Otherwise, returns
|
||
|
* a lowercase ISO 639-2/T language code.
|
||
|
* The ISO 639-2 language codes can be found on-line at
|
||
|
* <a href="ftp://dkuug.dk/i18n/iso-639-2.txt"><code>ftp://dkuug.dk/i18n/iso-639-2.txt</code></a>
|
||
|
* @exception MissingResourceException Throws MissingResourceException if the
|
||
|
* three-letter language abbreviation is not available for this locale.
|
||
|
*/
|
||
|
public String getISO3Language(){
|
||
|
return getISO3Language(localeID);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a three-letter abbreviation for this locale's language. If the locale
|
||
|
* doesn't specify a language, returns the empty string. Otherwise, returns
|
||
|
* a lowercase ISO 639-2/T language code.
|
||
|
* The ISO 639-2 language codes can be found on-line at
|
||
|
* <a href="ftp://dkuug.dk/i18n/iso-639-2.txt"><code>ftp://dkuug.dk/i18n/iso-639-2.txt</code></a>
|
||
|
* @exception MissingResourceException Throws MissingResourceException if the
|
||
|
* three-letter language abbreviation is not available for this locale.
|
||
|
*/
|
||
|
public static String getISO3Language(String localeID) {
|
||
|
return LocaleIDs.getISO3Language(getLanguage(localeID));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a three-letter abbreviation for this locale's country/region. If the locale
|
||
|
* doesn't specify a country, returns the empty string. Otherwise, returns
|
||
|
* an uppercase ISO 3166 3-letter country code.
|
||
|
* @exception MissingResourceException Throws MissingResourceException if the
|
||
|
* three-letter country abbreviation is not available for this locale.
|
||
|
*/
|
||
|
public String getISO3Country() {
|
||
|
return getISO3Country(localeID);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a three-letter abbreviation for this locale's country/region. If the locale
|
||
|
* doesn't specify a country, returns the empty string. Otherwise, returns
|
||
|
* an uppercase ISO 3166 3-letter country code.
|
||
|
* @exception MissingResourceException Throws MissingResourceException if the
|
||
|
* three-letter country abbreviation is not available for this locale.
|
||
|
*/
|
||
|
public static String getISO3Country(String localeID) {
|
||
|
return LocaleIDs.getISO3Country(getCountry(localeID));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Pairs of (language subtag, + or -) for finding out fast if common languages
|
||
|
* are LTR (minus) or RTL (plus).
|
||
|
*/
|
||
|
private static final String LANG_DIR_STRING =
|
||
|
"root-en-es-pt-zh-ja-ko-de-fr-it-ar+he+fa+ru-nl-pl-th-tr-";
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns whether this locale's script is written right-to-left.
|
||
|
* If there is no script subtag, then the likely script is used,
|
||
|
* see {@link #addLikelySubtags(ULocale)}.
|
||
|
* If no likely script is known, then false is returned.
|
||
|
*
|
||
|
* <p>A script is right-to-left according to the CLDR script metadata
|
||
|
* which corresponds to whether the script's letters have Bidi_Class=R or AL.
|
||
|
*
|
||
|
* <p>Returns true for "ar" and "en-Hebr", false for "zh" and "fa-Cyrl".
|
||
|
*
|
||
|
* @return true if the locale's script is written right-to-left
|
||
|
*/
|
||
|
public boolean isRightToLeft() {
|
||
|
String script = getScript();
|
||
|
if (script.length() == 0) {
|
||
|
// Fastpath: We know the likely scripts and their writing direction
|
||
|
// for some common languages.
|
||
|
String lang = getLanguage();
|
||
|
if (!lang.isEmpty()) {
|
||
|
int langIndex = LANG_DIR_STRING.indexOf(lang);
|
||
|
if (langIndex >= 0) {
|
||
|
switch (LANG_DIR_STRING.charAt(langIndex + lang.length())) {
|
||
|
case '-': return false;
|
||
|
case '+': return true;
|
||
|
default: break; // partial match of a longer code
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Otherwise, find the likely script.
|
||
|
ULocale likely = addLikelySubtags(this);
|
||
|
script = likely.getScript();
|
||
|
if (script.length() == 0) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
int scriptCode = UScript.getCodeFromName(script);
|
||
|
return UScript.isRightToLeft(scriptCode);
|
||
|
}
|
||
|
|
||
|
// display names
|
||
|
|
||
|
/**
|
||
|
* Returns this locale's language localized for display in the default <code>DISPLAY</code> locale.
|
||
|
* @return the localized language name.
|
||
|
* @see Category#DISPLAY
|
||
|
*/
|
||
|
public String getDisplayLanguage() {
|
||
|
return getDisplayLanguageInternal(this, getDefault(Category.DISPLAY), false);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns this locale's language localized for display in the provided locale.
|
||
|
* @param displayLocale the locale in which to display the name.
|
||
|
* @return the localized language name.
|
||
|
*/
|
||
|
public String getDisplayLanguage(ULocale displayLocale) {
|
||
|
return getDisplayLanguageInternal(this, displayLocale, false);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a locale's language localized for display in the provided locale.
|
||
|
* This is a cover for the ICU4C API.
|
||
|
* @param localeID the id of the locale whose language will be displayed
|
||
|
* @param displayLocaleID the id of the locale in which to display the name.
|
||
|
* @return the localized language name.
|
||
|
*/
|
||
|
public static String getDisplayLanguage(String localeID, String displayLocaleID) {
|
||
|
return getDisplayLanguageInternal(new ULocale(localeID), new ULocale(displayLocaleID),
|
||
|
false);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a locale's language localized for display in the provided locale.
|
||
|
* This is a cover for the ICU4C API.
|
||
|
* @param localeID the id of the locale whose language will be displayed.
|
||
|
* @param displayLocale the locale in which to display the name.
|
||
|
* @return the localized language name.
|
||
|
*/
|
||
|
public static String getDisplayLanguage(String localeID, ULocale displayLocale) {
|
||
|
return getDisplayLanguageInternal(new ULocale(localeID), displayLocale, false);
|
||
|
}
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns this locale's language localized for display in the default <code>DISPLAY</code> locale.
|
||
|
* If a dialect name is present in the data, then it is returned.
|
||
|
* @return the localized language name.
|
||
|
* @see Category#DISPLAY
|
||
|
*/
|
||
|
public String getDisplayLanguageWithDialect() {
|
||
|
return getDisplayLanguageInternal(this, getDefault(Category.DISPLAY), true);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns this locale's language localized for display in the provided locale.
|
||
|
* If a dialect name is present in the data, then it is returned.
|
||
|
* @param displayLocale the locale in which to display the name.
|
||
|
* @return the localized language name.
|
||
|
*/
|
||
|
public String getDisplayLanguageWithDialect(ULocale displayLocale) {
|
||
|
return getDisplayLanguageInternal(this, displayLocale, true);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a locale's language localized for display in the provided locale.
|
||
|
* If a dialect name is present in the data, then it is returned.
|
||
|
* This is a cover for the ICU4C API.
|
||
|
* @param localeID the id of the locale whose language will be displayed
|
||
|
* @param displayLocaleID the id of the locale in which to display the name.
|
||
|
* @return the localized language name.
|
||
|
*/
|
||
|
public static String getDisplayLanguageWithDialect(String localeID, String displayLocaleID) {
|
||
|
return getDisplayLanguageInternal(new ULocale(localeID), new ULocale(displayLocaleID),
|
||
|
true);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a locale's language localized for display in the provided locale.
|
||
|
* If a dialect name is present in the data, then it is returned.
|
||
|
* This is a cover for the ICU4C API.
|
||
|
* @param localeID the id of the locale whose language will be displayed.
|
||
|
* @param displayLocale the locale in which to display the name.
|
||
|
* @return the localized language name.
|
||
|
*/
|
||
|
public static String getDisplayLanguageWithDialect(String localeID, ULocale displayLocale) {
|
||
|
return getDisplayLanguageInternal(new ULocale(localeID), displayLocale, true);
|
||
|
}
|
||
|
|
||
|
private static String getDisplayLanguageInternal(ULocale locale, ULocale displayLocale,
|
||
|
boolean useDialect) {
|
||
|
String lang = useDialect ? locale.getBaseName() : locale.getLanguage();
|
||
|
return LocaleDisplayNames.getInstance(displayLocale).languageDisplayName(lang);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns this locale's script localized for display in the default <code>DISPLAY</code> locale.
|
||
|
* @return the localized script name.
|
||
|
* @see Category#DISPLAY
|
||
|
*/
|
||
|
public String getDisplayScript() {
|
||
|
return getDisplayScriptInternal(this, getDefault(Category.DISPLAY));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns this locale's script localized for display in the default <code>DISPLAY</code> locale.
|
||
|
* @return the localized script name.
|
||
|
* @see Category#DISPLAY
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide original deprecated declaration
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public String getDisplayScriptInContext() {
|
||
|
return getDisplayScriptInContextInternal(this, getDefault(Category.DISPLAY));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns this locale's script localized for display in the provided locale.
|
||
|
* @param displayLocale the locale in which to display the name.
|
||
|
* @return the localized script name.
|
||
|
*/
|
||
|
public String getDisplayScript(ULocale displayLocale) {
|
||
|
return getDisplayScriptInternal(this, displayLocale);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns this locale's script localized for display in the provided locale.
|
||
|
* @param displayLocale the locale in which to display the name.
|
||
|
* @return the localized script name.
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide original deprecated declaration
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public String getDisplayScriptInContext(ULocale displayLocale) {
|
||
|
return getDisplayScriptInContextInternal(this, displayLocale);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a locale's script localized for display in the provided locale.
|
||
|
* This is a cover for the ICU4C API.
|
||
|
* @param localeID the id of the locale whose script will be displayed
|
||
|
* @param displayLocaleID the id of the locale in which to display the name.
|
||
|
* @return the localized script name.
|
||
|
*/
|
||
|
public static String getDisplayScript(String localeID, String displayLocaleID) {
|
||
|
return getDisplayScriptInternal(new ULocale(localeID), new ULocale(displayLocaleID));
|
||
|
}
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a locale's script localized for display in the provided locale.
|
||
|
* This is a cover for the ICU4C API.
|
||
|
* @param localeID the id of the locale whose script will be displayed
|
||
|
* @param displayLocaleID the id of the locale in which to display the name.
|
||
|
* @return the localized script name.
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide original deprecated declaration
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public static String getDisplayScriptInContext(String localeID, String displayLocaleID) {
|
||
|
return getDisplayScriptInContextInternal(new ULocale(localeID), new ULocale(displayLocaleID));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a locale's script localized for display in the provided locale.
|
||
|
* @param localeID the id of the locale whose script will be displayed.
|
||
|
* @param displayLocale the locale in which to display the name.
|
||
|
* @return the localized script name.
|
||
|
*/
|
||
|
public static String getDisplayScript(String localeID, ULocale displayLocale) {
|
||
|
return getDisplayScriptInternal(new ULocale(localeID), displayLocale);
|
||
|
}
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a locale's script localized for display in the provided locale.
|
||
|
* @param localeID the id of the locale whose script will be displayed.
|
||
|
* @param displayLocale the locale in which to display the name.
|
||
|
* @return the localized script name.
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide original deprecated declaration
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public static String getDisplayScriptInContext(String localeID, ULocale displayLocale) {
|
||
|
return getDisplayScriptInContextInternal(new ULocale(localeID), displayLocale);
|
||
|
}
|
||
|
|
||
|
// displayLocaleID is canonical, localeID need not be since parsing will fix this.
|
||
|
private static String getDisplayScriptInternal(ULocale locale, ULocale displayLocale) {
|
||
|
return LocaleDisplayNames.getInstance(displayLocale)
|
||
|
.scriptDisplayName(locale.getScript());
|
||
|
}
|
||
|
|
||
|
private static String getDisplayScriptInContextInternal(ULocale locale, ULocale displayLocale) {
|
||
|
return LocaleDisplayNames.getInstance(displayLocale)
|
||
|
.scriptDisplayNameInContext(locale.getScript());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns this locale's country localized for display in the default <code>DISPLAY</code> locale.
|
||
|
* <b>Warning: </b>this is for the region part of a valid locale ID; it cannot just be the region code (like "FR").
|
||
|
* To get the display name for a region alone, or for other options, use {@link LocaleDisplayNames} instead.
|
||
|
* @return the localized country name.
|
||
|
* @see Category#DISPLAY
|
||
|
*/
|
||
|
public String getDisplayCountry() {
|
||
|
return getDisplayCountryInternal(this, getDefault(Category.DISPLAY));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns this locale's country localized for display in the provided locale.
|
||
|
* <b>Warning: </b>this is for the region part of a valid locale ID; it cannot just be the region code (like "FR").
|
||
|
* To get the display name for a region alone, or for other options, use {@link LocaleDisplayNames} instead.
|
||
|
* @param displayLocale the locale in which to display the name.
|
||
|
* @return the localized country name.
|
||
|
*/
|
||
|
public String getDisplayCountry(ULocale displayLocale){
|
||
|
return getDisplayCountryInternal(this, displayLocale);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a locale's country localized for display in the provided locale.
|
||
|
* <b>Warning: </b>this is for the region part of a valid locale ID; it cannot just be the region code (like "FR").
|
||
|
* To get the display name for a region alone, or for other options, use {@link LocaleDisplayNames} instead.
|
||
|
* This is a cover for the ICU4C API.
|
||
|
* @param localeID the id of the locale whose country will be displayed
|
||
|
* @param displayLocaleID the id of the locale in which to display the name.
|
||
|
* @return the localized country name.
|
||
|
*/
|
||
|
public static String getDisplayCountry(String localeID, String displayLocaleID) {
|
||
|
return getDisplayCountryInternal(new ULocale(localeID), new ULocale(displayLocaleID));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a locale's country localized for display in the provided locale.
|
||
|
* <b>Warning: </b>this is for the region part of a valid locale ID; it cannot just be the region code (like "FR").
|
||
|
* To get the display name for a region alone, or for other options, use {@link LocaleDisplayNames} instead.
|
||
|
* This is a cover for the ICU4C API.
|
||
|
* @param localeID the id of the locale whose country will be displayed.
|
||
|
* @param displayLocale the locale in which to display the name.
|
||
|
* @return the localized country name.
|
||
|
*/
|
||
|
public static String getDisplayCountry(String localeID, ULocale displayLocale) {
|
||
|
return getDisplayCountryInternal(new ULocale(localeID), displayLocale);
|
||
|
}
|
||
|
|
||
|
// displayLocaleID is canonical, localeID need not be since parsing will fix this.
|
||
|
private static String getDisplayCountryInternal(ULocale locale, ULocale displayLocale) {
|
||
|
return LocaleDisplayNames.getInstance(displayLocale)
|
||
|
.regionDisplayName(locale.getCountry());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns this locale's variant localized for display in the default <code>DISPLAY</code> locale.
|
||
|
* @return the localized variant name.
|
||
|
* @see Category#DISPLAY
|
||
|
*/
|
||
|
public String getDisplayVariant() {
|
||
|
return getDisplayVariantInternal(this, getDefault(Category.DISPLAY));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns this locale's variant localized for display in the provided locale.
|
||
|
* @param displayLocale the locale in which to display the name.
|
||
|
* @return the localized variant name.
|
||
|
*/
|
||
|
public String getDisplayVariant(ULocale displayLocale) {
|
||
|
return getDisplayVariantInternal(this, displayLocale);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a locale's variant localized for display in the provided locale.
|
||
|
* This is a cover for the ICU4C API.
|
||
|
* @param localeID the id of the locale whose variant will be displayed
|
||
|
* @param displayLocaleID the id of the locale in which to display the name.
|
||
|
* @return the localized variant name.
|
||
|
*/
|
||
|
public static String getDisplayVariant(String localeID, String displayLocaleID){
|
||
|
return getDisplayVariantInternal(new ULocale(localeID), new ULocale(displayLocaleID));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a locale's variant localized for display in the provided locale.
|
||
|
* This is a cover for the ICU4C API.
|
||
|
* @param localeID the id of the locale whose variant will be displayed.
|
||
|
* @param displayLocale the locale in which to display the name.
|
||
|
* @return the localized variant name.
|
||
|
*/
|
||
|
public static String getDisplayVariant(String localeID, ULocale displayLocale) {
|
||
|
return getDisplayVariantInternal(new ULocale(localeID), displayLocale);
|
||
|
}
|
||
|
|
||
|
private static String getDisplayVariantInternal(ULocale locale, ULocale displayLocale) {
|
||
|
return LocaleDisplayNames.getInstance(displayLocale)
|
||
|
.variantDisplayName(locale.getVariant());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a keyword localized for display in the default <code>DISPLAY</code> locale.
|
||
|
* @param keyword the keyword to be displayed.
|
||
|
* @return the localized keyword name.
|
||
|
* @see #getKeywords()
|
||
|
* @see Category#DISPLAY
|
||
|
*/
|
||
|
public static String getDisplayKeyword(String keyword) {
|
||
|
return getDisplayKeywordInternal(keyword, getDefault(Category.DISPLAY));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a keyword localized for display in the specified locale.
|
||
|
* @param keyword the keyword to be displayed.
|
||
|
* @param displayLocaleID the id of the locale in which to display the keyword.
|
||
|
* @return the localized keyword name.
|
||
|
* @see #getKeywords(String)
|
||
|
*/
|
||
|
public static String getDisplayKeyword(String keyword, String displayLocaleID) {
|
||
|
return getDisplayKeywordInternal(keyword, new ULocale(displayLocaleID));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a keyword localized for display in the specified locale.
|
||
|
* @param keyword the keyword to be displayed.
|
||
|
* @param displayLocale the locale in which to display the keyword.
|
||
|
* @return the localized keyword name.
|
||
|
* @see #getKeywords(String)
|
||
|
*/
|
||
|
public static String getDisplayKeyword(String keyword, ULocale displayLocale) {
|
||
|
return getDisplayKeywordInternal(keyword, displayLocale);
|
||
|
}
|
||
|
|
||
|
private static String getDisplayKeywordInternal(String keyword, ULocale displayLocale) {
|
||
|
return LocaleDisplayNames.getInstance(displayLocale).keyDisplayName(keyword);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a keyword value localized for display in the default <code>DISPLAY</code> locale.
|
||
|
* @param keyword the keyword whose value is to be displayed.
|
||
|
* @return the localized value name.
|
||
|
* @see Category#DISPLAY
|
||
|
*/
|
||
|
public String getDisplayKeywordValue(String keyword) {
|
||
|
return getDisplayKeywordValueInternal(this, keyword, getDefault(Category.DISPLAY));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a keyword value localized for display in the specified locale.
|
||
|
* @param keyword the keyword whose value is to be displayed.
|
||
|
* @param displayLocale the locale in which to display the value.
|
||
|
* @return the localized value name.
|
||
|
*/
|
||
|
public String getDisplayKeywordValue(String keyword, ULocale displayLocale) {
|
||
|
return getDisplayKeywordValueInternal(this, keyword, displayLocale);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a keyword value localized for display in the specified locale.
|
||
|
* This is a cover for the ICU4C API.
|
||
|
* @param localeID the id of the locale whose keyword value is to be displayed.
|
||
|
* @param keyword the keyword whose value is to be displayed.
|
||
|
* @param displayLocaleID the id of the locale in which to display the value.
|
||
|
* @return the localized value name.
|
||
|
*/
|
||
|
public static String getDisplayKeywordValue(String localeID, String keyword,
|
||
|
String displayLocaleID) {
|
||
|
return getDisplayKeywordValueInternal(new ULocale(localeID), keyword,
|
||
|
new ULocale(displayLocaleID));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a keyword value localized for display in the specified locale.
|
||
|
* This is a cover for the ICU4C API.
|
||
|
* @param localeID the id of the locale whose keyword value is to be displayed.
|
||
|
* @param keyword the keyword whose value is to be displayed.
|
||
|
* @param displayLocale the id of the locale in which to display the value.
|
||
|
* @return the localized value name.
|
||
|
*/
|
||
|
public static String getDisplayKeywordValue(String localeID, String keyword,
|
||
|
ULocale displayLocale) {
|
||
|
return getDisplayKeywordValueInternal(new ULocale(localeID), keyword, displayLocale);
|
||
|
}
|
||
|
|
||
|
// displayLocaleID is canonical, localeID need not be since parsing will fix this.
|
||
|
private static String getDisplayKeywordValueInternal(ULocale locale, String keyword,
|
||
|
ULocale displayLocale) {
|
||
|
keyword = AsciiUtil.toLowerString(keyword.trim());
|
||
|
String value = locale.getKeywordValue(keyword);
|
||
|
return LocaleDisplayNames.getInstance(displayLocale).keyValueDisplayName(keyword, value);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns this locale name localized for display in the default <code>DISPLAY</code> locale.
|
||
|
* @return the localized locale name.
|
||
|
* @see Category#DISPLAY
|
||
|
*/
|
||
|
public String getDisplayName() {
|
||
|
return getDisplayNameInternal(this, getDefault(Category.DISPLAY));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns this locale name localized for display in the provided locale.
|
||
|
* @param displayLocale the locale in which to display the locale name.
|
||
|
* @return the localized locale name.
|
||
|
*/
|
||
|
public String getDisplayName(ULocale displayLocale) {
|
||
|
return getDisplayNameInternal(this, displayLocale);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the locale ID localized for display in the provided locale.
|
||
|
* This is a cover for the ICU4C API.
|
||
|
* @param localeID the locale whose name is to be displayed.
|
||
|
* @param displayLocaleID the id of the locale in which to display the locale name.
|
||
|
* @return the localized locale name.
|
||
|
*/
|
||
|
public static String getDisplayName(String localeID, String displayLocaleID) {
|
||
|
return getDisplayNameInternal(new ULocale(localeID), new ULocale(displayLocaleID));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the locale ID localized for display in the provided locale.
|
||
|
* This is a cover for the ICU4C API.
|
||
|
* @param localeID the locale whose name is to be displayed.
|
||
|
* @param displayLocale the locale in which to display the locale name.
|
||
|
* @return the localized locale name.
|
||
|
*/
|
||
|
public static String getDisplayName(String localeID, ULocale displayLocale) {
|
||
|
return getDisplayNameInternal(new ULocale(localeID), displayLocale);
|
||
|
}
|
||
|
|
||
|
private static String getDisplayNameInternal(ULocale locale, ULocale displayLocale) {
|
||
|
return LocaleDisplayNames.getInstance(displayLocale).localeDisplayName(locale);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns this locale name localized for display in the default <code>DISPLAY</code> locale.
|
||
|
* If a dialect name is present in the locale data, then it is returned.
|
||
|
* @return the localized locale name.
|
||
|
* @see Category#DISPLAY
|
||
|
*/
|
||
|
public String getDisplayNameWithDialect() {
|
||
|
return getDisplayNameWithDialectInternal(this, getDefault(Category.DISPLAY));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns this locale name localized for display in the provided locale.
|
||
|
* If a dialect name is present in the locale data, then it is returned.
|
||
|
* @param displayLocale the locale in which to display the locale name.
|
||
|
* @return the localized locale name.
|
||
|
*/
|
||
|
public String getDisplayNameWithDialect(ULocale displayLocale) {
|
||
|
return getDisplayNameWithDialectInternal(this, displayLocale);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the locale ID localized for display in the provided locale.
|
||
|
* If a dialect name is present in the locale data, then it is returned.
|
||
|
* This is a cover for the ICU4C API.
|
||
|
* @param localeID the locale whose name is to be displayed.
|
||
|
* @param displayLocaleID the id of the locale in which to display the locale name.
|
||
|
* @return the localized locale name.
|
||
|
*/
|
||
|
public static String getDisplayNameWithDialect(String localeID, String displayLocaleID) {
|
||
|
return getDisplayNameWithDialectInternal(new ULocale(localeID),
|
||
|
new ULocale(displayLocaleID));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the locale ID localized for display in the provided locale.
|
||
|
* If a dialect name is present in the locale data, then it is returned.
|
||
|
* This is a cover for the ICU4C API.
|
||
|
* @param localeID the locale whose name is to be displayed.
|
||
|
* @param displayLocale the locale in which to display the locale name.
|
||
|
* @return the localized locale name.
|
||
|
*/
|
||
|
public static String getDisplayNameWithDialect(String localeID, ULocale displayLocale) {
|
||
|
return getDisplayNameWithDialectInternal(new ULocale(localeID), displayLocale);
|
||
|
}
|
||
|
|
||
|
private static String getDisplayNameWithDialectInternal(ULocale locale, ULocale displayLocale) {
|
||
|
return LocaleDisplayNames.getInstance(displayLocale, DialectHandling.DIALECT_NAMES)
|
||
|
.localeDisplayName(locale);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns this locale's layout orientation for characters. The possible
|
||
|
* values are "left-to-right", "right-to-left", "top-to-bottom" or
|
||
|
* "bottom-to-top".
|
||
|
* @return The locale's layout orientation for characters.
|
||
|
*/
|
||
|
public String getCharacterOrientation() {
|
||
|
return ICUResourceTableAccess.getTableString(ICUData.ICU_BASE_NAME, this,
|
||
|
"layout", "characters", "characters");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns this locale's layout orientation for lines. The possible
|
||
|
* values are "left-to-right", "right-to-left", "top-to-bottom" or
|
||
|
* "bottom-to-top".
|
||
|
* @return The locale's layout orientation for lines.
|
||
|
*/
|
||
|
public String getLineOrientation() {
|
||
|
return ICUResourceTableAccess.getTableString(ICUData.ICU_BASE_NAME, this,
|
||
|
"layout", "lines", "lines");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Selector for <tt>getLocale()</tt> indicating the locale of the
|
||
|
* resource containing the data. This is always at or above the
|
||
|
* valid locale. If the valid locale does not contain the
|
||
|
* specific data being requested, then the actual locale will be
|
||
|
* above the valid locale. If the object was not constructed from
|
||
|
* locale data, then the valid locale is <i>null</i>.
|
||
|
*
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
public static Type ACTUAL_LOCALE = new Type();
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Selector for <tt>getLocale()</tt> indicating the most specific
|
||
|
* locale for which any data exists. This is always at or above
|
||
|
* the requested locale, and at or below the actual locale. If
|
||
|
* the requested locale does not correspond to any resource data,
|
||
|
* then the valid locale will be above the requested locale. If
|
||
|
* the object was not constructed from locale data, then the
|
||
|
* actual locale is <i>null</i>.
|
||
|
*
|
||
|
* <p>Note: The valid locale will be returned correctly in ICU
|
||
|
* 3.0 or later. In ICU 2.8, it is not returned correctly.
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
public static Type VALID_LOCALE = new Type();
|
||
|
|
||
|
/**
|
||
|
* Opaque selector enum for <tt>getLocale()</tt>.
|
||
|
* @see android.icu.util.ULocale
|
||
|
* @see android.icu.util.ULocale#ACTUAL_LOCALE
|
||
|
* @see android.icu.util.ULocale#VALID_LOCALE
|
||
|
* @hide Only a subset of ICU is exposed in Android
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
public static final class Type {
|
||
|
private Type() {}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Based on a HTTP formatted list of acceptable locales, determine an available
|
||
|
* locale for the user. NullPointerException is thrown if acceptLanguageList or
|
||
|
* availableLocales is null. If fallback is non-null, it will contain true if a
|
||
|
* fallback locale (one not in the acceptLanguageList) was returned. The value on
|
||
|
* entry is ignored. ULocale will be one of the locales in availableLocales, or the
|
||
|
* ROOT ULocale if if a ROOT locale was used as a fallback (because nothing else in
|
||
|
* availableLocales matched). No ULocale array element should be null; behavior is
|
||
|
* undefined if this is the case.
|
||
|
*
|
||
|
* @param acceptLanguageList list in HTTP "Accept-Language:" format of acceptable locales
|
||
|
* @param availableLocales list of available locales. One of these will be returned.
|
||
|
* @param fallback if non-null, a 1-element array containing a boolean to be set with
|
||
|
* the fallback status
|
||
|
* @return one of the locales from the availableLocales list, or null if none match
|
||
|
*/
|
||
|
public static ULocale acceptLanguage(String acceptLanguageList, ULocale[] availableLocales,
|
||
|
boolean[] fallback) {
|
||
|
if (fallback != null) {
|
||
|
fallback[0] = true;
|
||
|
}
|
||
|
LocalePriorityList desired;
|
||
|
try {
|
||
|
desired = LocalePriorityList.add(acceptLanguageList).build();
|
||
|
} catch (IllegalArgumentException e) {
|
||
|
return null;
|
||
|
}
|
||
|
LocaleMatcher.Builder builder = LocaleMatcher.builder();
|
||
|
for (ULocale locale : availableLocales) {
|
||
|
builder.addSupportedULocale(locale);
|
||
|
}
|
||
|
LocaleMatcher matcher = builder.build();
|
||
|
LocaleMatcher.Result result = matcher.getBestMatchResult(desired);
|
||
|
if (result.getDesiredIndex() >= 0) {
|
||
|
if (fallback != null && result.getDesiredULocale().equals(result.getSupportedULocale())) {
|
||
|
fallback[0] = false;
|
||
|
}
|
||
|
return result.getSupportedULocale();
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Based on a list of acceptable locales, determine an available locale for the
|
||
|
* user. NullPointerException is thrown if acceptLanguageList or availableLocales is
|
||
|
* null. If fallback is non-null, it will contain true if a fallback locale (one not
|
||
|
* in the acceptLanguageList) was returned. The value on entry is ignored. ULocale
|
||
|
* will be one of the locales in availableLocales, or the ROOT ULocale if if a ROOT
|
||
|
* locale was used as a fallback (because nothing else in availableLocales matched).
|
||
|
* No ULocale array element should be null; behavior is undefined if this is the case.
|
||
|
*
|
||
|
* @param acceptLanguageList list of acceptable locales
|
||
|
* @param availableLocales list of available locales. One of these will be returned.
|
||
|
* @param fallback if non-null, a 1-element array containing a boolean to be set with
|
||
|
* the fallback status
|
||
|
* @return one of the locales from the availableLocales list, or null if none match
|
||
|
*/
|
||
|
|
||
|
public static ULocale acceptLanguage(ULocale[] acceptLanguageList, ULocale[] availableLocales,
|
||
|
boolean[] fallback) {
|
||
|
if (fallback != null) {
|
||
|
fallback[0] = true;
|
||
|
}
|
||
|
LocaleMatcher.Builder builder = LocaleMatcher.builder();
|
||
|
for (ULocale locale : availableLocales) {
|
||
|
builder.addSupportedULocale(locale);
|
||
|
}
|
||
|
LocaleMatcher matcher = builder.build();
|
||
|
LocaleMatcher.Result result;
|
||
|
if (acceptLanguageList.length == 1) {
|
||
|
result = matcher.getBestMatchResult(acceptLanguageList[0]);
|
||
|
} else {
|
||
|
result = matcher.getBestMatchResult(Arrays.asList(acceptLanguageList));
|
||
|
}
|
||
|
if (result.getDesiredIndex() >= 0) {
|
||
|
if (fallback != null && result.getDesiredULocale().equals(result.getSupportedULocale())) {
|
||
|
fallback[0] = false;
|
||
|
}
|
||
|
return result.getSupportedULocale();
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Based on a HTTP formatted list of acceptable locales, determine an available
|
||
|
* locale for the user. NullPointerException is thrown if acceptLanguageList or
|
||
|
* availableLocales is null. If fallback is non-null, it will contain true if a
|
||
|
* fallback locale (one not in the acceptLanguageList) was returned. The value on
|
||
|
* entry is ignored. ULocale will be one of the locales in availableLocales, or the
|
||
|
* ROOT ULocale if if a ROOT locale was used as a fallback (because nothing else in
|
||
|
* availableLocales matched). No ULocale array element should be null; behavior is
|
||
|
* undefined if this is the case. This function will choose a locale from the
|
||
|
* ULocale.getAvailableLocales() list as available.
|
||
|
*
|
||
|
* @param acceptLanguageList list in HTTP "Accept-Language:" format of acceptable locales
|
||
|
* @param fallback if non-null, a 1-element array containing a boolean to be set with
|
||
|
* the fallback status
|
||
|
* @return one of the locales from the ULocale.getAvailableLocales() list, or null if
|
||
|
* none match
|
||
|
*/
|
||
|
public static ULocale acceptLanguage(String acceptLanguageList, boolean[] fallback) {
|
||
|
return acceptLanguage(acceptLanguageList, ULocale.getAvailableLocales(),
|
||
|
fallback);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Based on an ordered array of acceptable locales, determine an available
|
||
|
* locale for the user. NullPointerException is thrown if acceptLanguageList or
|
||
|
* availableLocales is null. If fallback is non-null, it will contain true if a
|
||
|
* fallback locale (one not in the acceptLanguageList) was returned. The value on
|
||
|
* entry is ignored. ULocale will be one of the locales in availableLocales, or the
|
||
|
* ROOT ULocale if if a ROOT locale was used as a fallback (because nothing else in
|
||
|
* availableLocales matched). No ULocale array element should be null; behavior is
|
||
|
* undefined if this is the case. This function will choose a locale from the
|
||
|
* ULocale.getAvailableLocales() list as available.
|
||
|
*
|
||
|
* @param acceptLanguageList ordered array of acceptable locales (preferred are listed first)
|
||
|
* @param fallback if non-null, a 1-element array containing a boolean to be set with
|
||
|
* the fallback status
|
||
|
* @return one of the locales from the ULocale.getAvailableLocales() list, or null if none match
|
||
|
*/
|
||
|
public static ULocale acceptLanguage(ULocale[] acceptLanguageList, boolean[] fallback) {
|
||
|
return acceptLanguage(acceptLanguageList, ULocale.getAvailableLocales(),
|
||
|
fallback);
|
||
|
}
|
||
|
|
||
|
private static final String UNDEFINED_LANGUAGE = "und";
|
||
|
private static final String UNDEFINED_SCRIPT = "Zzzz";
|
||
|
private static final String UNDEFINED_REGION = "ZZ";
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Adds the likely subtags for a provided locale ID, per the algorithm
|
||
|
* described in the following CLDR technical report:
|
||
|
*
|
||
|
* http://www.unicode.org/reports/tr35/#Likely_Subtags
|
||
|
*
|
||
|
* If the provided ULocale instance is already in the maximal form, or there is no
|
||
|
* data available available for maximization, it will be returned. For example,
|
||
|
* "sh" cannot be maximized, since there is no reasonable maximization.
|
||
|
* Otherwise, a new ULocale instance with the maximal form is returned.
|
||
|
*
|
||
|
* Examples:
|
||
|
*
|
||
|
* "en" maximizes to "en_Latn_US"
|
||
|
*
|
||
|
* "de" maximizes to "de_Latn_DE"
|
||
|
*
|
||
|
* "sr" maximizes to "sr_Cyrl_RS"
|
||
|
*
|
||
|
* "zh_Hani" maximizes to "zh_Hani_CN"
|
||
|
*
|
||
|
* @param loc The ULocale to maximize
|
||
|
* @return The maximized ULocale instance.
|
||
|
*/
|
||
|
public static ULocale addLikelySubtags(ULocale loc) {
|
||
|
String[] tags = new String[3];
|
||
|
String trailing = null;
|
||
|
|
||
|
int trailingIndex = parseTagString(
|
||
|
loc.localeID,
|
||
|
tags);
|
||
|
|
||
|
if (trailingIndex < loc.localeID.length()) {
|
||
|
trailing = loc.localeID.substring(trailingIndex);
|
||
|
}
|
||
|
|
||
|
LSR max = LikelySubtags.INSTANCE.makeMaximizedLsrFrom(
|
||
|
new ULocale(loc.getLanguage(), loc.getScript(), loc.getCountry()), true);
|
||
|
String newLocaleID = createTagString(max.language, max.script, max.region,
|
||
|
trailing);
|
||
|
|
||
|
return newLocaleID == null ? loc : new ULocale(newLocaleID);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Minimizes the subtags for a provided locale ID, per the algorithm described
|
||
|
* in the following CLDR technical report:<blockquote>
|
||
|
*
|
||
|
* <a href="http://www.unicode.org/reports/tr35/#Likely_Subtags"
|
||
|
*>http://www.unicode.org/reports/tr35/#Likely_Subtags</a></blockquote>
|
||
|
*
|
||
|
* If the provided ULocale instance is already in the minimal form, or there
|
||
|
* is no data available for minimization, it will be returned. Since the
|
||
|
* minimization algorithm relies on proper maximization, see the comments
|
||
|
* for addLikelySubtags for reasons why there might not be any data.
|
||
|
*
|
||
|
* Examples:<pre>
|
||
|
*
|
||
|
* "en_Latn_US" minimizes to "en"
|
||
|
*
|
||
|
* "de_Latn_US" minimizes to "de"
|
||
|
*
|
||
|
* "sr_Cyrl_RS" minimizes to "sr"
|
||
|
*
|
||
|
* "zh_Hant_TW" minimizes to "zh_TW" (The region is preferred to the
|
||
|
* script, and minimizing to "zh" would imply "zh_Hans_CN".) </pre>
|
||
|
*
|
||
|
* @param loc The ULocale to minimize
|
||
|
* @return The minimized ULocale instance.
|
||
|
*/
|
||
|
public static ULocale minimizeSubtags(ULocale loc) {
|
||
|
return minimizeSubtags(loc, Minimize.FAVOR_REGION);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Options for minimizeSubtags.
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide Only a subset of ICU is exposed in Android
|
||
|
* @hide original deprecated declaration
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public enum Minimize {
|
||
|
/**
|
||
|
* Favor including the script, when either the region <b>or</b> the script could be suppressed, but not both.
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
FAVOR_SCRIPT,
|
||
|
/**
|
||
|
* Favor including the region, when either the region <b>or</b> the script could be suppressed, but not both.
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
FAVOR_REGION
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Minimizes the subtags for a provided locale ID, per the algorithm described
|
||
|
* in the following CLDR technical report:<blockquote>
|
||
|
*
|
||
|
* <a href="http://www.unicode.org/reports/tr35/#Likely_Subtags"
|
||
|
*>http://www.unicode.org/reports/tr35/#Likely_Subtags</a></blockquote>
|
||
|
*
|
||
|
* If the provided ULocale instance is already in the minimal form, or there
|
||
|
* is no data available for minimization, it will be returned. Since the
|
||
|
* minimization algorithm relies on proper maximization, see the comments
|
||
|
* for addLikelySubtags for reasons why there might not be any data.
|
||
|
*
|
||
|
* Examples:<pre>
|
||
|
*
|
||
|
* "en_Latn_US" minimizes to "en"
|
||
|
*
|
||
|
* "de_Latn_US" minimizes to "de"
|
||
|
*
|
||
|
* "sr_Cyrl_RS" minimizes to "sr"
|
||
|
*
|
||
|
* "zh_Hant_TW" minimizes to "zh_TW" if fieldToFavor == {@link Minimize#FAVOR_REGION}
|
||
|
* "zh_Hant_TW" minimizes to "zh_Hant" if fieldToFavor == {@link Minimize#FAVOR_SCRIPT}
|
||
|
* </pre>
|
||
|
* The fieldToFavor only has an effect if either the region or the script could be suppressed, but not both.
|
||
|
* @param loc The ULocale to minimize
|
||
|
* @param fieldToFavor Indicate which should be preferred, when either the region <b>or</b> the script could be suppressed, but not both.
|
||
|
* @return The minimized ULocale instance.
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide original deprecated declaration
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public static ULocale minimizeSubtags(ULocale loc, Minimize fieldToFavor) {
|
||
|
String[] tags = new String[3];
|
||
|
String trailing = null;
|
||
|
|
||
|
int trailingIndex = parseTagString(
|
||
|
loc.localeID,
|
||
|
tags);
|
||
|
|
||
|
if (trailingIndex < loc.localeID.length()) {
|
||
|
trailing = loc.localeID.substring(trailingIndex);
|
||
|
}
|
||
|
|
||
|
LSR lsr = LikelySubtags.INSTANCE.minimizeSubtags(
|
||
|
loc.getLanguage(), loc.getScript(), loc.getCountry(), fieldToFavor);
|
||
|
String newLocaleID = createTagString(lsr.language, lsr.script, lsr.region,
|
||
|
trailing);
|
||
|
|
||
|
return newLocaleID == null ? loc : new ULocale(newLocaleID);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A trivial utility function that checks for a null
|
||
|
* reference or checks the length of the supplied String.
|
||
|
*
|
||
|
* @param string The string to check
|
||
|
*
|
||
|
* @return true if the String is empty, or if the reference is null.
|
||
|
*/
|
||
|
private static boolean isEmptyString(String string) {
|
||
|
return string == null || string.length() == 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Append a tag to a StringBuilder, adding the separator if necessary.The tag must
|
||
|
* not be a zero-length string.
|
||
|
*
|
||
|
* @param tag The tag to add.
|
||
|
* @param buffer The output buffer.
|
||
|
**/
|
||
|
private static void appendTag(String tag, StringBuilder buffer) {
|
||
|
if (buffer.length() != 0) {
|
||
|
buffer.append(UNDERSCORE);
|
||
|
}
|
||
|
|
||
|
buffer.append(tag);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create a tag string from the supplied parameters. The lang, script and region
|
||
|
* parameters may be null references.
|
||
|
*
|
||
|
* If any of the language, script or region parameters are empty, and the alternateTags
|
||
|
* parameter is not null, it will be parsed for potential language, script and region tags
|
||
|
* to be used when constructing the new tag. If the alternateTags parameter is null, or
|
||
|
* it contains no language tag, the default tag for the unknown language is used.
|
||
|
*
|
||
|
* @param lang The language tag to use.
|
||
|
* @param script The script tag to use.
|
||
|
* @param region The region tag to use.
|
||
|
* @param trailing Any trailing data to append to the new tag.
|
||
|
* @param alternateTags A string containing any alternate tags.
|
||
|
* @return The new tag string.
|
||
|
**/
|
||
|
private static String createTagString(String lang, String script, String region,
|
||
|
String trailing) {
|
||
|
|
||
|
LocaleIDParser parser = null;
|
||
|
|
||
|
StringBuilder tag = new StringBuilder();
|
||
|
|
||
|
if (!isEmptyString(lang)) {
|
||
|
appendTag(
|
||
|
lang,
|
||
|
tag);
|
||
|
} else {
|
||
|
/*
|
||
|
* Append the value for an unknown language, if
|
||
|
* we found no language.
|
||
|
*/
|
||
|
appendTag(
|
||
|
UNDEFINED_LANGUAGE,
|
||
|
tag);
|
||
|
}
|
||
|
|
||
|
if (!isEmptyString(script)) {
|
||
|
appendTag(
|
||
|
script,
|
||
|
tag);
|
||
|
}
|
||
|
|
||
|
if (!isEmptyString(region)) {
|
||
|
appendTag(
|
||
|
region,
|
||
|
tag);
|
||
|
}
|
||
|
|
||
|
if (trailing != null && trailing.length() > 1) {
|
||
|
/*
|
||
|
* The current ICU format expects two underscores
|
||
|
* will separate the variant from the preceding
|
||
|
* parts of the tag, if there is no region.
|
||
|
*/
|
||
|
int separators = 0;
|
||
|
|
||
|
if (trailing.charAt(0) == UNDERSCORE) {
|
||
|
if (trailing.charAt(1) == UNDERSCORE) {
|
||
|
separators = 2;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
separators = 1;
|
||
|
}
|
||
|
|
||
|
if (!isEmptyString(region)) {
|
||
|
/*
|
||
|
* If we appended a region, we may need to strip
|
||
|
* the extra separator from the variant portion.
|
||
|
*/
|
||
|
if (separators == 2) {
|
||
|
tag.append(trailing.substring(1));
|
||
|
}
|
||
|
else {
|
||
|
tag.append(trailing);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
/*
|
||
|
* If we did not append a region, we may need to add
|
||
|
* an extra separator to the variant portion.
|
||
|
*/
|
||
|
if (separators == 1) {
|
||
|
tag.append(UNDERSCORE);
|
||
|
}
|
||
|
tag.append(trailing);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return tag.toString();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse the language, script, and region subtags from a tag string, and return the results.
|
||
|
*
|
||
|
* This function does not return the canonical strings for the unknown script and region.
|
||
|
*
|
||
|
* @param localeID The locale ID to parse.
|
||
|
* @param tags An array of three String references to return the subtag strings.
|
||
|
* @return The number of chars of the localeID parameter consumed.
|
||
|
**/
|
||
|
private static int parseTagString(String localeID, String tags[]) {
|
||
|
LocaleIDParser parser = new LocaleIDParser(localeID);
|
||
|
|
||
|
String lang = parser.getLanguage();
|
||
|
String script = parser.getScript();
|
||
|
String region = parser.getCountry();
|
||
|
|
||
|
if (isEmptyString(lang)) {
|
||
|
tags[0] = UNDEFINED_LANGUAGE;
|
||
|
}
|
||
|
else {
|
||
|
tags[0] = lang;
|
||
|
}
|
||
|
|
||
|
if (script.equals(UNDEFINED_SCRIPT)) {
|
||
|
tags[1] = "";
|
||
|
}
|
||
|
else {
|
||
|
tags[1] = script;
|
||
|
}
|
||
|
|
||
|
if (region.equals(UNDEFINED_REGION)) {
|
||
|
tags[2] = "";
|
||
|
}
|
||
|
else {
|
||
|
tags[2] = region;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Search for the variant. If there is one, then return the index of
|
||
|
* the preceding separator.
|
||
|
* If there's no variant, search for the keyword delimiter,
|
||
|
* and return its index. Otherwise, return the length of the
|
||
|
* string.
|
||
|
*
|
||
|
* $TOTO(dbertoni) we need to take into account that we might
|
||
|
* find a part of the language as the variant, since it can
|
||
|
* can have a variant portion that is long enough to contain
|
||
|
* the same characters as the variant.
|
||
|
*/
|
||
|
String variant = parser.getVariant();
|
||
|
|
||
|
if (!isEmptyString(variant)){
|
||
|
int index = localeID.indexOf(variant);
|
||
|
|
||
|
|
||
|
return index > 0 ? index - 1 : index;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int index = localeID.indexOf('@');
|
||
|
|
||
|
return index == -1 ? localeID.length() : index;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// --------------------------------
|
||
|
// BCP47/OpenJDK APIs
|
||
|
// --------------------------------
|
||
|
|
||
|
/**
|
||
|
* The key for the private use locale extension ('x').
|
||
|
*
|
||
|
* @see #getExtension(char)
|
||
|
* @see Builder#setExtension(char, String)
|
||
|
*/
|
||
|
public static final char PRIVATE_USE_EXTENSION = 'x';
|
||
|
|
||
|
/**
|
||
|
* The key for Unicode locale extension ('u').
|
||
|
*
|
||
|
* @see #getExtension(char)
|
||
|
* @see Builder#setExtension(char, String)
|
||
|
*/
|
||
|
public static final char UNICODE_LOCALE_EXTENSION = 'u';
|
||
|
|
||
|
/**
|
||
|
* Returns the extension (or private use) value associated with
|
||
|
* the specified key, or null if there is no extension
|
||
|
* associated with the key. To be well-formed, the key must be one
|
||
|
* of <code>[0-9A-Za-z]</code>. Keys are case-insensitive, so
|
||
|
* for example 'z' and 'Z' represent the same extension.
|
||
|
*
|
||
|
* @param key the extension key
|
||
|
* @return The extension, or null if this locale defines no
|
||
|
* extension for the specified key.
|
||
|
* @throws IllegalArgumentException if key is not well-formed
|
||
|
* @see #PRIVATE_USE_EXTENSION
|
||
|
* @see #UNICODE_LOCALE_EXTENSION
|
||
|
*/
|
||
|
public String getExtension(char key) {
|
||
|
if (!LocaleExtensions.isValidKey(key)) {
|
||
|
throw new IllegalArgumentException("Invalid extension key: " + key);
|
||
|
}
|
||
|
return extensions().getExtensionValue(key);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the set of extension keys associated with this locale, or the
|
||
|
* empty set if it has no extensions. The returned set is unmodifiable.
|
||
|
* The keys will all be lower-case.
|
||
|
*
|
||
|
* @return the set of extension keys, or the empty set if this locale has
|
||
|
* no extensions
|
||
|
*/
|
||
|
public Set<Character> getExtensionKeys() {
|
||
|
return extensions().getKeys();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the set of unicode locale attributes associated with
|
||
|
* this locale, or the empty set if it has no attributes. The
|
||
|
* returned set is unmodifiable.
|
||
|
*
|
||
|
* @return The set of attributes.
|
||
|
*/
|
||
|
public Set<String> getUnicodeLocaleAttributes() {
|
||
|
return extensions().getUnicodeLocaleAttributes();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the Unicode locale type associated with the specified Unicode locale key
|
||
|
* for this locale. Returns the empty string for keys that are defined with no type.
|
||
|
* Returns null if the key is not defined. Keys are case-insensitive. The key must
|
||
|
* be two alphanumeric characters ([0-9a-zA-Z]), or an IllegalArgumentException is
|
||
|
* thrown.
|
||
|
*
|
||
|
* @param key the Unicode locale key
|
||
|
* @return The Unicode locale type associated with the key, or null if the
|
||
|
* locale does not define the key.
|
||
|
* @throws IllegalArgumentException if the key is not well-formed
|
||
|
* @throws NullPointerException if <code>key</code> is null
|
||
|
*/
|
||
|
public String getUnicodeLocaleType(String key) {
|
||
|
if (!LocaleExtensions.isValidUnicodeLocaleKey(key)) {
|
||
|
throw new IllegalArgumentException("Invalid Unicode locale key: " + key);
|
||
|
}
|
||
|
return extensions().getUnicodeLocaleType(key);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the set of Unicode locale keys defined by this locale, or the empty set if
|
||
|
* this locale has none. The returned set is immutable. Keys are all lower case.
|
||
|
*
|
||
|
* @return The set of Unicode locale keys, or the empty set if this locale has
|
||
|
* no Unicode locale keywords.
|
||
|
*/
|
||
|
public Set<String> getUnicodeLocaleKeys() {
|
||
|
return extensions().getUnicodeLocaleKeys();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a well-formed IETF BCP 47 language tag representing
|
||
|
* this locale.
|
||
|
*
|
||
|
* <p>If this <code>ULocale</code> has a language, script, country, or
|
||
|
* variant that does not satisfy the IETF BCP 47 language tag
|
||
|
* syntax requirements, this method handles these fields as
|
||
|
* described below:
|
||
|
*
|
||
|
* <p><b>Language:</b> If language is empty, or not well-formed
|
||
|
* (for example "a" or "e2"), it will be emitted as "und" (Undetermined).
|
||
|
*
|
||
|
* <p><b>Script:</b> If script is not well-formed (for example "12"
|
||
|
* or "Latin"), it will be omitted.
|
||
|
*
|
||
|
* <p><b>Country:</b> If country is not well-formed (for example "12"
|
||
|
* or "USA"), it will be omitted.
|
||
|
*
|
||
|
* <p><b>Variant:</b> If variant <b>is</b> well-formed, each sub-segment
|
||
|
* (delimited by '-' or '_') is emitted as a subtag. Otherwise:
|
||
|
* <ul>
|
||
|
*
|
||
|
* <li>if all sub-segments match <code>[0-9a-zA-Z]{1,8}</code>
|
||
|
* (for example "WIN" or "Oracle_JDK_Standard_Edition"), the first
|
||
|
* ill-formed sub-segment and all following will be appended to
|
||
|
* the private use subtag. The first appended subtag will be
|
||
|
* "lvariant", followed by the sub-segments in order, separated by
|
||
|
* hyphen. For example, "x-lvariant-WIN",
|
||
|
* "Oracle-x-lvariant-JDK-Standard-Edition".
|
||
|
*
|
||
|
* <li>if any sub-segment does not match
|
||
|
* <code>[0-9a-zA-Z]{1,8}</code>, the variant will be truncated
|
||
|
* and the problematic sub-segment and all following sub-segments
|
||
|
* will be omitted. If the remainder is non-empty, it will be
|
||
|
* emitted as a private use subtag as above (even if the remainder
|
||
|
* turns out to be well-formed). For example,
|
||
|
* "Solaris_isjustthecoolestthing" is emitted as
|
||
|
* "x-lvariant-Solaris", not as "solaris".</li></ul>
|
||
|
*
|
||
|
* <p><b>Note:</b> Although the language tag created by this
|
||
|
* method is well-formed (satisfies the syntax requirements
|
||
|
* defined by the IETF BCP 47 specification), it is not
|
||
|
* necessarily a valid BCP 47 language tag. For example,
|
||
|
* <pre>
|
||
|
* new Locale("xx", "YY").toLanguageTag();</pre>
|
||
|
*
|
||
|
* will return "xx-YY", but the language subtag "xx" and the
|
||
|
* region subtag "YY" are invalid because they are not registered
|
||
|
* in the IANA Language Subtag Registry.
|
||
|
*
|
||
|
* @return a BCP47 language tag representing the locale
|
||
|
* @see #forLanguageTag(String)
|
||
|
*/
|
||
|
public String toLanguageTag() {
|
||
|
BaseLocale base = base();
|
||
|
LocaleExtensions exts = extensions();
|
||
|
|
||
|
if (base.getVariant().equalsIgnoreCase("POSIX")) {
|
||
|
// special handling for variant POSIX
|
||
|
base = BaseLocale.getInstance(base.getLanguage(), base.getScript(), base.getRegion(), "");
|
||
|
if (exts.getUnicodeLocaleType("va") == null) {
|
||
|
// add va-posix
|
||
|
InternalLocaleBuilder ilocbld = new InternalLocaleBuilder();
|
||
|
try {
|
||
|
ilocbld.setLocale(BaseLocale.ROOT, exts);
|
||
|
ilocbld.setUnicodeLocaleKeyword("va", "posix");
|
||
|
exts = ilocbld.getLocaleExtensions();
|
||
|
} catch (LocaleSyntaxException e) {
|
||
|
// this should not happen
|
||
|
throw new RuntimeException(e);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
LanguageTag tag = LanguageTag.parseLocale(base, exts);
|
||
|
|
||
|
StringBuilder buf = new StringBuilder();
|
||
|
String subtag = tag.getLanguage();
|
||
|
if (subtag.length() > 0) {
|
||
|
buf.append(LanguageTag.canonicalizeLanguage(subtag));
|
||
|
}
|
||
|
|
||
|
subtag = tag.getScript();
|
||
|
if (subtag.length() > 0) {
|
||
|
buf.append(LanguageTag.SEP);
|
||
|
buf.append(LanguageTag.canonicalizeScript(subtag));
|
||
|
}
|
||
|
|
||
|
subtag = tag.getRegion();
|
||
|
if (subtag.length() > 0) {
|
||
|
buf.append(LanguageTag.SEP);
|
||
|
buf.append(LanguageTag.canonicalizeRegion(subtag));
|
||
|
}
|
||
|
|
||
|
List<String>subtags = tag.getVariants();
|
||
|
// ICU-20478: Sort variants per UTS35.
|
||
|
ArrayList<String> variants = new ArrayList<>(subtags);
|
||
|
Collections.sort(variants);
|
||
|
for (String s : variants) {
|
||
|
buf.append(LanguageTag.SEP);
|
||
|
buf.append(LanguageTag.canonicalizeVariant(s));
|
||
|
}
|
||
|
|
||
|
subtags = tag.getExtensions();
|
||
|
for (String s : subtags) {
|
||
|
buf.append(LanguageTag.SEP);
|
||
|
buf.append(LanguageTag.canonicalizeExtension(s));
|
||
|
}
|
||
|
|
||
|
subtag = tag.getPrivateuse();
|
||
|
if (subtag.length() > 0) {
|
||
|
if (buf.length() == 0) {
|
||
|
buf.append(UNDEFINED_LANGUAGE);
|
||
|
}
|
||
|
buf.append(LanguageTag.SEP);
|
||
|
buf.append(LanguageTag.PRIVATEUSE).append(LanguageTag.SEP);
|
||
|
buf.append(LanguageTag.canonicalizePrivateuse(subtag));
|
||
|
}
|
||
|
|
||
|
return buf.toString();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a locale for the specified IETF BCP 47 language tag string.
|
||
|
*
|
||
|
* <p>If the specified language tag contains any ill-formed subtags,
|
||
|
* the first such subtag and all following subtags are ignored. Compare
|
||
|
* to {@link ULocale.Builder#setLanguageTag} which throws an exception
|
||
|
* in this case.
|
||
|
*
|
||
|
* <p>The following <b>conversions</b> are performed:
|
||
|
* <ul>
|
||
|
*
|
||
|
* <li>The language code "und" is mapped to language "".
|
||
|
*
|
||
|
* <li>The portion of a private use subtag prefixed by "lvariant",
|
||
|
* if any, is removed and appended to the variant field in the
|
||
|
* result locale (without case normalization). If it is then
|
||
|
* empty, the private use subtag is discarded:
|
||
|
*
|
||
|
* <pre>
|
||
|
* ULocale loc;
|
||
|
* loc = ULocale.forLanguageTag("en-US-x-lvariant-icu4j);
|
||
|
* loc.getVariant(); // returns "ICU4J"
|
||
|
* loc.getExtension('x'); // returns null
|
||
|
*
|
||
|
* loc = Locale.forLanguageTag("de-icu4j-x-URP-lvariant-Abc-Def");
|
||
|
* loc.getVariant(); // returns "ICU4J_ABC_DEF"
|
||
|
* loc.getExtension('x'); // returns "urp"
|
||
|
* </pre>
|
||
|
*
|
||
|
* <li>When the languageTag argument contains an extlang subtag,
|
||
|
* the first such subtag is used as the language, and the primary
|
||
|
* language subtag and other extlang subtags are ignored:
|
||
|
*
|
||
|
* <pre>
|
||
|
* ULocale.forLanguageTag("ar-aao").getLanguage(); // returns "aao"
|
||
|
* ULocale.forLanguageTag("en-abc-def-us").toString(); // returns "abc_US"
|
||
|
* </pre>
|
||
|
*
|
||
|
* <li>Case is normalized. Language is normalized to lower case,
|
||
|
* script to title case, country to upper case, variant to upper case,
|
||
|
* and extensions to lower case.
|
||
|
*
|
||
|
* </ul>
|
||
|
*
|
||
|
* <p>This implements the 'Language-Tag' production of BCP 47, and so
|
||
|
* supports legacy language tags (marked as “Type: grandfathered” in BCP 47)
|
||
|
* (regular and irregular) as well as private use language tags.
|
||
|
*
|
||
|
* <p>Stand-alone private use tags are represented as empty language and extension 'x-whatever',
|
||
|
* and legacy tags are converted to their canonical replacements where they exist.
|
||
|
*
|
||
|
* <p>Note that a few legacy tags have no modern replacement;
|
||
|
* these will be converted using the fallback described in
|
||
|
* the first paragraph, so some information might be lost.
|
||
|
*
|
||
|
* <p><b>Note</b>: there is no guarantee that <code>toLanguageTag</code>
|
||
|
* and <code>forLanguageTag</code> will round-trip.
|
||
|
*
|
||
|
* @param languageTag the language tag
|
||
|
* @return The locale that best represents the language tag.
|
||
|
* @throws NullPointerException if <code>languageTag</code> is <code>null</code>
|
||
|
* @see #toLanguageTag()
|
||
|
* @see ULocale.Builder#setLanguageTag(String)
|
||
|
*/
|
||
|
public static ULocale forLanguageTag(String languageTag) {
|
||
|
LanguageTag tag = LanguageTag.parse(languageTag, null);
|
||
|
InternalLocaleBuilder bldr = new InternalLocaleBuilder();
|
||
|
bldr.setLanguageTag(tag);
|
||
|
return getInstance(bldr.getBaseLocale(), bldr.getLocaleExtensions());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Converts the specified keyword (legacy key, or BCP 47 Unicode locale
|
||
|
* extension key) to the equivalent BCP 47 Unicode locale extension key.
|
||
|
* For example, BCP 47 Unicode locale extension key "co" is returned for
|
||
|
* the input keyword "collation".
|
||
|
* <p>
|
||
|
* When the specified keyword is unknown, but satisfies the BCP syntax,
|
||
|
* then the lower-case version of the input keyword will be returned.
|
||
|
* For example,
|
||
|
* <code>toUnicodeLocaleKey("ZZ")</code> returns "zz".
|
||
|
*
|
||
|
* @param keyword the input locale keyword (either legacy key
|
||
|
* such as "collation" or BCP 47 Unicode locale extension
|
||
|
* key such as "co").
|
||
|
* @return the well-formed BCP 47 Unicode locale extension key,
|
||
|
* or null if the specified locale keyword cannot be mapped
|
||
|
* to a well-formed BCP 47 Unicode locale extension key.
|
||
|
* @see #toLegacyKey(String)
|
||
|
*/
|
||
|
public static String toUnicodeLocaleKey(String keyword) {
|
||
|
String bcpKey = KeyTypeData.toBcpKey(keyword);
|
||
|
if (bcpKey == null && UnicodeLocaleExtension.isKey(keyword)) {
|
||
|
// unknown keyword, but syntax is fine..
|
||
|
bcpKey = AsciiUtil.toLowerString(keyword);
|
||
|
}
|
||
|
return bcpKey;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Converts the specified keyword value (legacy type, or BCP 47
|
||
|
* Unicode locale extension type) to the well-formed BCP 47 Unicode locale
|
||
|
* extension type for the specified keyword (category). For example, BCP 47
|
||
|
* Unicode locale extension type "phonebk" is returned for the input
|
||
|
* keyword value "phonebook", with the keyword "collation" (or "co").
|
||
|
* <p>
|
||
|
* When the specified keyword is not recognized, but the specified value
|
||
|
* satisfies the syntax of the BCP 47 Unicode locale extension type,
|
||
|
* or when the specified keyword allows 'variable' type and the specified
|
||
|
* value satisfies the syntax, the lower-case version of the input value
|
||
|
* will be returned. For example,
|
||
|
* <code>toUnicodeLocaleType("Foo", "Bar")</code> returns "bar",
|
||
|
* <code>toUnicodeLocaleType("variableTop", "00A4")</code> returns "00a4".
|
||
|
*
|
||
|
* @param keyword the locale keyword (either legacy key such as
|
||
|
* "collation" or BCP 47 Unicode locale extension
|
||
|
* key such as "co").
|
||
|
* @param value the locale keyword value (either legacy type
|
||
|
* such as "phonebook" or BCP 47 Unicode locale extension
|
||
|
* type such as "phonebk").
|
||
|
* @return the well-formed BCP47 Unicode locale extension type,
|
||
|
* or null if the locale keyword value cannot be mapped to
|
||
|
* a well-formed BCP 47 Unicode locale extension type.
|
||
|
* @see #toLegacyType(String, String)
|
||
|
*/
|
||
|
public static String toUnicodeLocaleType(String keyword, String value) {
|
||
|
String bcpType = KeyTypeData.toBcpType(keyword, value, null, null);
|
||
|
if (bcpType == null && UnicodeLocaleExtension.isType(value)) {
|
||
|
// unknown keyword, but syntax is fine..
|
||
|
bcpType = AsciiUtil.toLowerString(value);
|
||
|
}
|
||
|
return bcpType;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Converts the specified keyword (BCP 47 Unicode locale extension key, or
|
||
|
* legacy key) to the legacy key. For example, legacy key "collation" is
|
||
|
* returned for the input BCP 47 Unicode locale extension key "co".
|
||
|
*
|
||
|
* @param keyword the input locale keyword (either BCP 47 Unicode locale
|
||
|
* extension key or legacy key).
|
||
|
* @return the well-formed legacy key, or null if the specified
|
||
|
* keyword cannot be mapped to a well-formed legacy key.
|
||
|
* @see #toUnicodeLocaleKey(String)
|
||
|
*/
|
||
|
public static String toLegacyKey(String keyword) {
|
||
|
String legacyKey = KeyTypeData.toLegacyKey(keyword);
|
||
|
if (legacyKey == null) {
|
||
|
// Checks if the specified locale key is well-formed with the legacy locale syntax.
|
||
|
//
|
||
|
// Note:
|
||
|
// Neither ICU nor LDML/CLDR provides the definition of keyword syntax.
|
||
|
// However, a key should not contain '=' obviously. For now, all existing
|
||
|
// keys are using ASCII alphabetic letters only. We won't add any new key
|
||
|
// that is not compatible with the BCP 47 syntax. Therefore, we assume
|
||
|
// a valid key consist from [0-9a-zA-Z], no symbols.
|
||
|
if (keyword.matches("[0-9a-zA-Z]+")) {
|
||
|
legacyKey = AsciiUtil.toLowerString(keyword);
|
||
|
}
|
||
|
}
|
||
|
return legacyKey;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Converts the specified keyword value (BCP 47 Unicode locale extension type,
|
||
|
* or legacy type or type alias) to the canonical legacy type. For example,
|
||
|
* the legacy type "phonebook" is returned for the input BCP 47 Unicode
|
||
|
* locale extension type "phonebk" with the keyword "collation" (or "co").
|
||
|
* <p>
|
||
|
* When the specified keyword is not recognized, but the specified value
|
||
|
* satisfies the syntax of legacy key, or when the specified keyword
|
||
|
* allows 'variable' type and the specified value satisfies the syntax,
|
||
|
* the lower-case version of the input value will be returned.
|
||
|
* For example,
|
||
|
* <code>toLegacyType("Foo", "Bar")</code> returns "bar",
|
||
|
* <code>toLegacyType("vt", "00A4")</code> returns "00a4".
|
||
|
*
|
||
|
* @param keyword the locale keyword (either legacy keyword such as
|
||
|
* "collation" or BCP 47 Unicode locale extension
|
||
|
* key such as "co").
|
||
|
* @param value the locale keyword value (either BCP 47 Unicode locale
|
||
|
* extension type such as "phonebk" or legacy keyword value
|
||
|
* such as "phonebook").
|
||
|
* @return the well-formed legacy type, or null if the specified
|
||
|
* keyword value cannot be mapped to a well-formed legacy
|
||
|
* type.
|
||
|
* @see #toUnicodeLocaleType(String, String)
|
||
|
*/
|
||
|
public static String toLegacyType(String keyword, String value) {
|
||
|
String legacyType = KeyTypeData.toLegacyType(keyword, value, null, null);
|
||
|
if (legacyType == null) {
|
||
|
// Checks if the specified locale type is well-formed with the legacy locale syntax.
|
||
|
//
|
||
|
// Note:
|
||
|
// Neither ICU nor LDML/CLDR provides the definition of keyword syntax.
|
||
|
// However, a type should not contain '=' obviously. For now, all existing
|
||
|
// types are using ASCII alphabetic letters with a few symbol letters. We won't
|
||
|
// add any new type that is not compatible with the BCP 47 syntax except timezone
|
||
|
// IDs. For now, we assume a valid type start with [0-9a-zA-Z], but may contain
|
||
|
// '-' '_' '/' in the middle.
|
||
|
if (value.matches("[0-9a-zA-Z]+([_/\\-][0-9a-zA-Z]+)*")) {
|
||
|
legacyType = AsciiUtil.toLowerString(value);
|
||
|
}
|
||
|
}
|
||
|
return legacyType;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <code>Builder</code> is used to build instances of <code>ULocale</code>
|
||
|
* from values configured by the setters. Unlike the <code>ULocale</code>
|
||
|
* constructors, the <code>Builder</code> checks if a value configured by a
|
||
|
* setter satisfies the syntax requirements defined by the <code>ULocale</code>
|
||
|
* class. A <code>ULocale</code> object created by a <code>Builder</code> is
|
||
|
* well-formed and can be transformed to a well-formed IETF BCP 47 language tag
|
||
|
* without losing information.
|
||
|
*
|
||
|
* <p><b>Note:</b> The <code>ULocale</code> class does not provide any
|
||
|
* syntactic restrictions on variant, while BCP 47 requires each variant
|
||
|
* subtag to be 5 to 8 alphanumerics or a single numeric followed by 3
|
||
|
* alphanumerics. The method <code>setVariant</code> throws
|
||
|
* <code>IllformedLocaleException</code> for a variant that does not satisfy
|
||
|
* this restriction. If it is necessary to support such a variant, use a
|
||
|
* ULocale constructor. However, keep in mind that a <code>ULocale</code>
|
||
|
* object created this way might lose the variant information when
|
||
|
* transformed to a BCP 47 language tag.
|
||
|
*
|
||
|
* <p>The following example shows how to create a <code>Locale</code> object
|
||
|
* with the <code>Builder</code>.
|
||
|
* <blockquote>
|
||
|
* <pre>
|
||
|
* ULocale aLocale = new Builder().setLanguage("sr").setScript("Latn").setRegion("RS").build();
|
||
|
* </pre>
|
||
|
* </blockquote>
|
||
|
*
|
||
|
* <p>Builders can be reused; <code>clear()</code> resets all
|
||
|
* fields to their default values.
|
||
|
*
|
||
|
* @see ULocale#toLanguageTag()
|
||
|
*/
|
||
|
public static final class Builder {
|
||
|
|
||
|
private final InternalLocaleBuilder _locbld;
|
||
|
|
||
|
/**
|
||
|
* Constructs an empty Builder. The default value of all
|
||
|
* fields, extensions, and private use information is the
|
||
|
* empty string.
|
||
|
*/
|
||
|
public Builder() {
|
||
|
_locbld = new InternalLocaleBuilder();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Resets the <code>Builder</code> to match the provided
|
||
|
* <code>locale</code>. Existing state is discarded.
|
||
|
*
|
||
|
* <p>All fields of the locale must be well-formed, see {@link Locale}.
|
||
|
*
|
||
|
* <p>Locales with any ill-formed fields cause
|
||
|
* <code>IllformedLocaleException</code> to be thrown.
|
||
|
*
|
||
|
* @param locale the locale
|
||
|
* @return This builder.
|
||
|
* @throws IllformedLocaleException if <code>locale</code> has
|
||
|
* any ill-formed fields.
|
||
|
* @throws NullPointerException if <code>locale</code> is null.
|
||
|
*/
|
||
|
public Builder setLocale(ULocale locale) {
|
||
|
try {
|
||
|
_locbld.setLocale(locale.base(), locale.extensions());
|
||
|
} catch (LocaleSyntaxException e) {
|
||
|
throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Resets the Builder to match the provided IETF BCP 47
|
||
|
* language tag. Discards the existing state. Null and the
|
||
|
* empty string cause the builder to be reset, like {@link
|
||
|
* #clear}. Legacy tags (see {@link
|
||
|
* ULocale#forLanguageTag}) are converted to their canonical
|
||
|
* form before being processed. Otherwise, the language tag
|
||
|
* must be well-formed (see {@link ULocale}) or an exception is
|
||
|
* thrown (unlike <code>ULocale.forLanguageTag</code>, which
|
||
|
* just discards ill-formed and following portions of the
|
||
|
* tag).
|
||
|
*
|
||
|
* @param languageTag the language tag
|
||
|
* @return This builder.
|
||
|
* @throws IllformedLocaleException if <code>languageTag</code> is ill-formed
|
||
|
* @see ULocale#forLanguageTag(String)
|
||
|
*/
|
||
|
public Builder setLanguageTag(String languageTag) {
|
||
|
ParseStatus sts = new ParseStatus();
|
||
|
LanguageTag tag = LanguageTag.parse(languageTag, sts);
|
||
|
if (sts.isError()) {
|
||
|
throw new IllformedLocaleException(sts.getErrorMessage(), sts.getErrorIndex());
|
||
|
}
|
||
|
_locbld.setLanguageTag(tag);
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the language. If <code>language</code> is the empty string or
|
||
|
* null, the language in this <code>Builder</code> is removed. Otherwise,
|
||
|
* the language must be <a href="./Locale.html#def_language">well-formed</a>
|
||
|
* or an exception is thrown.
|
||
|
*
|
||
|
* <p>The typical language value is a two or three-letter language
|
||
|
* code as defined in ISO639.
|
||
|
*
|
||
|
* @param language the language
|
||
|
* @return This builder.
|
||
|
* @throws IllformedLocaleException if <code>language</code> is ill-formed
|
||
|
*/
|
||
|
public Builder setLanguage(String language) {
|
||
|
try {
|
||
|
_locbld.setLanguage(language);
|
||
|
} catch (LocaleSyntaxException e) {
|
||
|
throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the script. If <code>script</code> is null or the empty string,
|
||
|
* the script in this <code>Builder</code> is removed.
|
||
|
* Otherwise, the script must be well-formed or an exception is thrown.
|
||
|
*
|
||
|
* <p>The typical script value is a four-letter script code as defined by ISO 15924.
|
||
|
*
|
||
|
* @param script the script
|
||
|
* @return This builder.
|
||
|
* @throws IllformedLocaleException if <code>script</code> is ill-formed
|
||
|
*/
|
||
|
public Builder setScript(String script) {
|
||
|
try {
|
||
|
_locbld.setScript(script);
|
||
|
} catch (LocaleSyntaxException e) {
|
||
|
throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the region. If region is null or the empty string, the region
|
||
|
* in this <code>Builder</code> is removed. Otherwise,
|
||
|
* the region must be well-formed or an exception is thrown.
|
||
|
*
|
||
|
* <p>The typical region value is a two-letter ISO 3166 code or a
|
||
|
* three-digit UN M.49 area code.
|
||
|
*
|
||
|
* <p>The country value in the <code>Locale</code> created by the
|
||
|
* <code>Builder</code> is always normalized to upper case.
|
||
|
*
|
||
|
* @param region the region
|
||
|
* @return This builder.
|
||
|
* @throws IllformedLocaleException if <code>region</code> is ill-formed
|
||
|
*/
|
||
|
public Builder setRegion(String region) {
|
||
|
try {
|
||
|
_locbld.setRegion(region);
|
||
|
} catch (LocaleSyntaxException e) {
|
||
|
throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the variant. If variant is null or the empty string, the
|
||
|
* variant in this <code>Builder</code> is removed. Otherwise, it
|
||
|
* must consist of one or more well-formed subtags, or an exception is thrown.
|
||
|
*
|
||
|
* <p><b>Note:</b> This method checks if <code>variant</code>
|
||
|
* satisfies the IETF BCP 47 variant subtag's syntax requirements,
|
||
|
* and normalizes the value to lowercase letters. However,
|
||
|
* the <code>ULocale</code> class does not impose any syntactic
|
||
|
* restriction on variant. To set such a variant,
|
||
|
* use a ULocale constructor.
|
||
|
*
|
||
|
* @param variant the variant
|
||
|
* @return This builder.
|
||
|
* @throws IllformedLocaleException if <code>variant</code> is ill-formed
|
||
|
*/
|
||
|
public Builder setVariant(String variant) {
|
||
|
try {
|
||
|
_locbld.setVariant(variant);
|
||
|
} catch (LocaleSyntaxException e) {
|
||
|
throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the extension for the given key. If the value is null or the
|
||
|
* empty string, the extension is removed. Otherwise, the extension
|
||
|
* must be well-formed or an exception is thrown.
|
||
|
*
|
||
|
* <p><b>Note:</b> The key {@link ULocale#UNICODE_LOCALE_EXTENSION
|
||
|
* UNICODE_LOCALE_EXTENSION} ('u') is used for the Unicode locale extension.
|
||
|
* Setting a value for this key replaces any existing Unicode locale key/type
|
||
|
* pairs with those defined in the extension.
|
||
|
*
|
||
|
* <p><b>Note:</b> The key {@link ULocale#PRIVATE_USE_EXTENSION
|
||
|
* PRIVATE_USE_EXTENSION} ('x') is used for the private use code. To be
|
||
|
* well-formed, the value for this key needs only to have subtags of one to
|
||
|
* eight alphanumeric characters, not two to eight as in the general case.
|
||
|
*
|
||
|
* @param key the extension key
|
||
|
* @param value the extension value
|
||
|
* @return This builder.
|
||
|
* @throws IllformedLocaleException if <code>key</code> is illegal
|
||
|
* or <code>value</code> is ill-formed
|
||
|
* @see #setUnicodeLocaleKeyword(String, String)
|
||
|
*/
|
||
|
public Builder setExtension(char key, String value) {
|
||
|
try {
|
||
|
_locbld.setExtension(key, value);
|
||
|
} catch (LocaleSyntaxException e) {
|
||
|
throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the Unicode locale keyword type for the given key. If the type
|
||
|
* is null, the Unicode keyword is removed. Otherwise, the key must be
|
||
|
* non-null and both key and type must be well-formed or an exception
|
||
|
* is thrown.
|
||
|
*
|
||
|
* <p>Keys and types are converted to lower case.
|
||
|
*
|
||
|
* <p><b>Note</b>:Setting the 'u' extension via {@link #setExtension}
|
||
|
* replaces all Unicode locale keywords with those defined in the
|
||
|
* extension.
|
||
|
*
|
||
|
* @param key the Unicode locale key
|
||
|
* @param type the Unicode locale type
|
||
|
* @return This builder.
|
||
|
* @throws IllformedLocaleException if <code>key</code> or <code>type</code>
|
||
|
* is ill-formed
|
||
|
* @throws NullPointerException if <code>key</code> is null
|
||
|
* @see #setExtension(char, String)
|
||
|
*/
|
||
|
public Builder setUnicodeLocaleKeyword(String key, String type) {
|
||
|
try {
|
||
|
_locbld.setUnicodeLocaleKeyword(key, type);
|
||
|
} catch (LocaleSyntaxException e) {
|
||
|
throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Adds a unicode locale attribute, if not already present, otherwise
|
||
|
* has no effect. The attribute must not be null and must be well-formed
|
||
|
* or an exception is thrown.
|
||
|
*
|
||
|
* @param attribute the attribute
|
||
|
* @return This builder.
|
||
|
* @throws NullPointerException if <code>attribute</code> is null
|
||
|
* @throws IllformedLocaleException if <code>attribute</code> is ill-formed
|
||
|
* @see #setExtension(char, String)
|
||
|
*/
|
||
|
public Builder addUnicodeLocaleAttribute(String attribute) {
|
||
|
try {
|
||
|
_locbld.addUnicodeLocaleAttribute(attribute);
|
||
|
} catch (LocaleSyntaxException e) {
|
||
|
throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Removes a unicode locale attribute, if present, otherwise has no
|
||
|
* effect. The attribute must not be null and must be well-formed
|
||
|
* or an exception is thrown.
|
||
|
*
|
||
|
* <p>Attribute comparison for removal is case-insensitive.
|
||
|
*
|
||
|
* @param attribute the attribute
|
||
|
* @return This builder.
|
||
|
* @throws NullPointerException if <code>attribute</code> is null
|
||
|
* @throws IllformedLocaleException if <code>attribute</code> is ill-formed
|
||
|
* @see #setExtension(char, String)
|
||
|
*/
|
||
|
public Builder removeUnicodeLocaleAttribute(String attribute) {
|
||
|
try {
|
||
|
_locbld.removeUnicodeLocaleAttribute(attribute);
|
||
|
} catch (LocaleSyntaxException e) {
|
||
|
throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Resets the builder to its initial, empty state.
|
||
|
*
|
||
|
* @return this builder
|
||
|
*/
|
||
|
public Builder clear() {
|
||
|
_locbld.clear();
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Resets the extensions to their initial, empty state.
|
||
|
* Language, script, region and variant are unchanged.
|
||
|
*
|
||
|
* @return this builder
|
||
|
* @see #setExtension(char, String)
|
||
|
*/
|
||
|
public Builder clearExtensions() {
|
||
|
_locbld.clearExtensions();
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an instance of <code>ULocale</code> created from the fields set
|
||
|
* on this builder.
|
||
|
*
|
||
|
* @return a new Locale
|
||
|
*/
|
||
|
public ULocale build() {
|
||
|
return getInstance(_locbld.getBaseLocale(), _locbld.getLocaleExtensions());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static ULocale getInstance(BaseLocale base, LocaleExtensions exts) {
|
||
|
String id = lscvToID(base.getLanguage(), base.getScript(), base.getRegion(),
|
||
|
base.getVariant());
|
||
|
|
||
|
Set<Character> extKeys = exts.getKeys();
|
||
|
if (!extKeys.isEmpty()) {
|
||
|
// legacy locale ID assume Unicode locale keywords and
|
||
|
// other extensions are at the same level.
|
||
|
// e.g. @a=ext-for-aa;calendar=japanese;m=ext-for-mm;x=priv-use
|
||
|
|
||
|
TreeMap<String, String> kwds = new TreeMap<>();
|
||
|
for (Character key : extKeys) {
|
||
|
Extension ext = exts.getExtension(key);
|
||
|
if (ext instanceof UnicodeLocaleExtension) {
|
||
|
UnicodeLocaleExtension uext = (UnicodeLocaleExtension)ext;
|
||
|
Set<String> ukeys = uext.getUnicodeLocaleKeys();
|
||
|
for (String bcpKey : ukeys) {
|
||
|
String bcpType = uext.getUnicodeLocaleType(bcpKey);
|
||
|
// convert to legacy key/type
|
||
|
String lkey = toLegacyKey(bcpKey);
|
||
|
String ltype = toLegacyType(bcpKey, ((bcpType.length() == 0) ? "yes" : bcpType)); // use "yes" as the value of typeless keywords
|
||
|
// special handling for u-va-posix, since this is a variant, not a keyword
|
||
|
if (lkey.equals("va") && ltype.equals("posix") && base.getVariant().length() == 0) {
|
||
|
id = id + "_POSIX";
|
||
|
} else {
|
||
|
kwds.put(lkey, ltype);
|
||
|
}
|
||
|
}
|
||
|
// Mapping Unicode locale attribute to the special keyword, attribute=xxx-yyy
|
||
|
Set<String> uattributes = uext.getUnicodeLocaleAttributes();
|
||
|
if (uattributes.size() > 0) {
|
||
|
StringBuilder attrbuf = new StringBuilder();
|
||
|
for (String attr : uattributes) {
|
||
|
if (attrbuf.length() > 0) {
|
||
|
attrbuf.append('-');
|
||
|
}
|
||
|
attrbuf.append(attr);
|
||
|
}
|
||
|
kwds.put(LOCALE_ATTRIBUTE_KEY, attrbuf.toString());
|
||
|
}
|
||
|
} else {
|
||
|
kwds.put(String.valueOf(key), ext.getValue());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!kwds.isEmpty()) {
|
||
|
StringBuilder buf = new StringBuilder(id);
|
||
|
buf.append("@");
|
||
|
Set<Map.Entry<String, String>> kset = kwds.entrySet();
|
||
|
boolean insertSep = false;
|
||
|
for (Map.Entry<String, String> kwd : kset) {
|
||
|
if (insertSep) {
|
||
|
buf.append(";");
|
||
|
} else {
|
||
|
insertSep = true;
|
||
|
}
|
||
|
buf.append(kwd.getKey());
|
||
|
buf.append("=");
|
||
|
buf.append(kwd.getValue());
|
||
|
}
|
||
|
|
||
|
id = buf.toString();
|
||
|
}
|
||
|
}
|
||
|
return new ULocale(id);
|
||
|
}
|
||
|
|
||
|
private BaseLocale base() {
|
||
|
if (baseLocale == null) {
|
||
|
String language, script, region, variant;
|
||
|
language = script = region = variant = "";
|
||
|
if (!equals(ULocale.ROOT)) {
|
||
|
LocaleIDParser lp = new LocaleIDParser(localeID);
|
||
|
language = lp.getLanguage();
|
||
|
script = lp.getScript();
|
||
|
region = lp.getCountry();
|
||
|
variant = lp.getVariant();
|
||
|
}
|
||
|
baseLocale = BaseLocale.getInstance(language, script, region, variant);
|
||
|
}
|
||
|
return baseLocale;
|
||
|
}
|
||
|
|
||
|
private LocaleExtensions extensions() {
|
||
|
if (extensions == null) {
|
||
|
Iterator<String> kwitr = getKeywords();
|
||
|
if (kwitr == null) {
|
||
|
extensions = LocaleExtensions.EMPTY_EXTENSIONS;
|
||
|
} else {
|
||
|
InternalLocaleBuilder intbld = new InternalLocaleBuilder();
|
||
|
while (kwitr.hasNext()) {
|
||
|
String key = kwitr.next();
|
||
|
if (key.equals(LOCALE_ATTRIBUTE_KEY)) {
|
||
|
// special keyword used for representing Unicode locale attributes
|
||
|
String[] uattributes = getKeywordValue(key).split("[-_]");
|
||
|
for (String uattr : uattributes) {
|
||
|
try {
|
||
|
intbld.addUnicodeLocaleAttribute(uattr);
|
||
|
} catch (LocaleSyntaxException e) {
|
||
|
// ignore and fall through
|
||
|
}
|
||
|
}
|
||
|
} else if (key.length() >= 2) {
|
||
|
String bcpKey = toUnicodeLocaleKey(key);
|
||
|
String bcpType = toUnicodeLocaleType(key, getKeywordValue(key));
|
||
|
if (bcpKey != null && bcpType != null) {
|
||
|
try {
|
||
|
intbld.setUnicodeLocaleKeyword(bcpKey, bcpType);
|
||
|
} catch (LocaleSyntaxException e) {
|
||
|
// ignore and fall through
|
||
|
}
|
||
|
}
|
||
|
} else if (key.length() == 1 && (key.charAt(0) != UNICODE_LOCALE_EXTENSION)) {
|
||
|
try {
|
||
|
intbld.setExtension(key.charAt(0), getKeywordValue(key).replace("_",
|
||
|
LanguageTag.SEP));
|
||
|
} catch (LocaleSyntaxException e) {
|
||
|
// ignore and fall through
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
extensions = intbld.getLocaleExtensions();
|
||
|
}
|
||
|
}
|
||
|
return extensions;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* JDK Locale Helper
|
||
|
*/
|
||
|
private static final class JDKLocaleHelper {
|
||
|
// Java 7 has java.util.Locale.Category.
|
||
|
// Android API level 21..23 do not yet have it; only API level 24 (Nougat) adds it.
|
||
|
// https://developer.android.com/reference/java/util/Locale.Category
|
||
|
private static boolean hasLocaleCategories = false;
|
||
|
|
||
|
private static Method mGetDefault;
|
||
|
private static Method mSetDefault;
|
||
|
private static Object eDISPLAY;
|
||
|
private static Object eFORMAT;
|
||
|
|
||
|
static {
|
||
|
do {
|
||
|
try {
|
||
|
Class<?> cCategory = null;
|
||
|
Class<?>[] classes = Locale.class.getDeclaredClasses();
|
||
|
for (Class<?> c : classes) {
|
||
|
if (c.getName().equals("java.util.Locale$Category")) {
|
||
|
cCategory = c;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (cCategory == null) {
|
||
|
break;
|
||
|
}
|
||
|
mGetDefault = Locale.class.getDeclaredMethod("getDefault", cCategory);
|
||
|
mSetDefault = Locale.class.getDeclaredMethod("setDefault", cCategory, Locale.class);
|
||
|
|
||
|
Method mName = cCategory.getMethod("name", (Class[]) null);
|
||
|
Object[] enumConstants = cCategory.getEnumConstants();
|
||
|
for (Object e : enumConstants) {
|
||
|
String catVal = (String)mName.invoke(e, (Object[])null);
|
||
|
if (catVal.equals("DISPLAY")) {
|
||
|
eDISPLAY = e;
|
||
|
} else if (catVal.equals("FORMAT")) {
|
||
|
eFORMAT = e;
|
||
|
}
|
||
|
}
|
||
|
if (eDISPLAY == null || eFORMAT == null) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
hasLocaleCategories = true;
|
||
|
} catch (NoSuchMethodException e) {
|
||
|
} catch (IllegalArgumentException e) {
|
||
|
} catch (IllegalAccessException e) {
|
||
|
} catch (InvocationTargetException e) {
|
||
|
} catch (SecurityException e) {
|
||
|
// TODO : report?
|
||
|
}
|
||
|
} while (false);
|
||
|
}
|
||
|
|
||
|
private JDKLocaleHelper() {
|
||
|
}
|
||
|
|
||
|
public static boolean hasLocaleCategories() {
|
||
|
return hasLocaleCategories;
|
||
|
}
|
||
|
|
||
|
public static ULocale toULocale(Locale loc) {
|
||
|
String language = loc.getLanguage();
|
||
|
String script = "";
|
||
|
String country = loc.getCountry();
|
||
|
String variant = loc.getVariant();
|
||
|
|
||
|
Set<String> attributes = null;
|
||
|
Map<String, String> keywords = null;
|
||
|
|
||
|
script = loc.getScript();
|
||
|
Set<Character> extKeys = loc.getExtensionKeys();
|
||
|
if (!extKeys.isEmpty()) {
|
||
|
for (Character extKey : extKeys) {
|
||
|
if (extKey.charValue() == 'u') {
|
||
|
// Found Unicode locale extension
|
||
|
|
||
|
// attributes
|
||
|
@SuppressWarnings("unchecked")
|
||
|
Set<String> uAttributes = loc.getUnicodeLocaleAttributes();
|
||
|
if (!uAttributes.isEmpty()) {
|
||
|
attributes = new TreeSet<>();
|
||
|
for (String attr : uAttributes) {
|
||
|
attributes.add(attr);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// keywords
|
||
|
Set<String> uKeys = loc.getUnicodeLocaleKeys();
|
||
|
for (String kwKey : uKeys) {
|
||
|
String kwVal = loc.getUnicodeLocaleType(kwKey);
|
||
|
if (kwVal != null) {
|
||
|
if (kwKey.equals("va")) {
|
||
|
// va-* is interpreted as a variant
|
||
|
variant = (variant.length() == 0) ? kwVal : kwVal + "_" + variant;
|
||
|
} else {
|
||
|
if (keywords == null) {
|
||
|
keywords = new TreeMap<>();
|
||
|
}
|
||
|
keywords.put(kwKey, kwVal);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
String extVal = loc.getExtension(extKey);
|
||
|
if (extVal != null) {
|
||
|
if (keywords == null) {
|
||
|
keywords = new TreeMap<>();
|
||
|
}
|
||
|
keywords.put(String.valueOf(extKey), extVal);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// JDK locale no_NO_NY is not interpreted as Nynorsk by ICU,
|
||
|
// and it should be transformed to nn_NO.
|
||
|
|
||
|
// Note: JDK7+ unerstand both no_NO_NY and nn_NO. When convert
|
||
|
// ICU locale to JDK, we do not need to map nn_NO back to no_NO_NY.
|
||
|
|
||
|
if (language.equals("no") && country.equals("NO") && variant.equals("NY")) {
|
||
|
language = "nn";
|
||
|
variant = "";
|
||
|
}
|
||
|
|
||
|
// Constructing ID
|
||
|
StringBuilder buf = new StringBuilder(language);
|
||
|
|
||
|
if (script.length() > 0) {
|
||
|
buf.append('_');
|
||
|
buf.append(script);
|
||
|
}
|
||
|
|
||
|
if (country.length() > 0) {
|
||
|
buf.append('_');
|
||
|
buf.append(country);
|
||
|
}
|
||
|
|
||
|
if (variant.length() > 0) {
|
||
|
if (country.length() == 0) {
|
||
|
buf.append('_');
|
||
|
}
|
||
|
buf.append('_');
|
||
|
buf.append(variant);
|
||
|
}
|
||
|
|
||
|
if (attributes != null) {
|
||
|
// transform Unicode attributes into a keyword
|
||
|
StringBuilder attrBuf = new StringBuilder();
|
||
|
for (String attr : attributes) {
|
||
|
if (attrBuf.length() != 0) {
|
||
|
attrBuf.append('-');
|
||
|
}
|
||
|
attrBuf.append(attr);
|
||
|
}
|
||
|
if (keywords == null) {
|
||
|
keywords = new TreeMap<>();
|
||
|
}
|
||
|
keywords.put(LOCALE_ATTRIBUTE_KEY, attrBuf.toString());
|
||
|
}
|
||
|
|
||
|
if (keywords != null) {
|
||
|
buf.append('@');
|
||
|
boolean addSep = false;
|
||
|
for (Entry<String, String> kwEntry : keywords.entrySet()) {
|
||
|
String kwKey = kwEntry.getKey();
|
||
|
String kwVal = kwEntry.getValue();
|
||
|
|
||
|
if (kwKey.length() != 1) {
|
||
|
// Unicode locale key
|
||
|
kwKey = toLegacyKey(kwKey);
|
||
|
// use "yes" as the value of typeless keywords
|
||
|
kwVal = toLegacyType(kwKey, ((kwVal.length() == 0) ? "yes" : kwVal));
|
||
|
}
|
||
|
|
||
|
if (addSep) {
|
||
|
buf.append(';');
|
||
|
} else {
|
||
|
addSep = true;
|
||
|
}
|
||
|
buf.append(kwKey);
|
||
|
buf.append('=');
|
||
|
buf.append(kwVal);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return new ULocale(getName(buf.toString()), loc);
|
||
|
}
|
||
|
|
||
|
public static Locale toLocale(ULocale uloc) {
|
||
|
Locale loc = null;
|
||
|
String ulocStr = uloc.getName();
|
||
|
if (uloc.getScript().length() > 0 || ulocStr.contains("@")) {
|
||
|
// With script or keywords available, the best way
|
||
|
// to get a mapped Locale is to go through a language tag.
|
||
|
// A Locale with script or keywords can only have variants
|
||
|
// that is 1 to 8 alphanum. If this ULocale has a variant
|
||
|
// subtag not satisfying the criteria, the variant subtag
|
||
|
// will be lost.
|
||
|
String tag = uloc.toLanguageTag();
|
||
|
|
||
|
// Workaround for variant casing problem:
|
||
|
//
|
||
|
// The variant field in ICU is case insensitive and normalized
|
||
|
// to upper case letters by getVariant(), while
|
||
|
// the variant field in JDK Locale is case sensitive.
|
||
|
// ULocale#toLanguageTag use lower case characters for
|
||
|
// BCP 47 variant and private use x-lvariant.
|
||
|
//
|
||
|
// Locale#forLanguageTag in JDK preserves character casing
|
||
|
// for variant. Because ICU always normalizes variant to
|
||
|
// upper case, we convert language tag to upper case here.
|
||
|
tag = AsciiUtil.toUpperString(tag);
|
||
|
loc = Locale.forLanguageTag(tag);
|
||
|
}
|
||
|
if (loc == null) {
|
||
|
// Without script or keywords, use a Locale constructor,
|
||
|
// so we can preserve any ill-formed variants.
|
||
|
loc = new Locale(uloc.getLanguage(), uloc.getCountry(), uloc.getVariant());
|
||
|
}
|
||
|
return loc;
|
||
|
}
|
||
|
|
||
|
public static Locale getDefault(Category category) {
|
||
|
if (hasLocaleCategories) {
|
||
|
Object cat = null;
|
||
|
switch (category) {
|
||
|
case DISPLAY:
|
||
|
cat = eDISPLAY;
|
||
|
break;
|
||
|
case FORMAT:
|
||
|
cat = eFORMAT;
|
||
|
break;
|
||
|
}
|
||
|
if (cat != null) {
|
||
|
try {
|
||
|
return (Locale)mGetDefault.invoke(null, cat);
|
||
|
} catch (InvocationTargetException e) {
|
||
|
// fall through - use the base default
|
||
|
} catch (IllegalArgumentException e) {
|
||
|
// fall through - use the base default
|
||
|
} catch (IllegalAccessException e) {
|
||
|
// fall through - use the base default
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return Locale.getDefault();
|
||
|
}
|
||
|
|
||
|
public static void setDefault(Category category, Locale newLocale) {
|
||
|
if (hasLocaleCategories) {
|
||
|
Object cat = null;
|
||
|
switch (category) {
|
||
|
case DISPLAY:
|
||
|
cat = eDISPLAY;
|
||
|
break;
|
||
|
case FORMAT:
|
||
|
cat = eFORMAT;
|
||
|
break;
|
||
|
}
|
||
|
if (cat != null) {
|
||
|
try {
|
||
|
mSetDefault.invoke(null, cat, newLocale);
|
||
|
} catch (InvocationTargetException e) {
|
||
|
// fall through - no effects
|
||
|
} catch (IllegalArgumentException e) {
|
||
|
// fall through - no effects
|
||
|
} catch (IllegalAccessException e) {
|
||
|
// fall through - no effects
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|