ICU-21029 LocaleMatcher: add option to turn off default locale
diff --git a/icu4c/source/common/localematcher.cpp b/icu4c/source/common/localematcher.cpp
index 85db8c8..a7a1137 100644
--- a/icu4c/source/common/localematcher.cpp
+++ b/icu4c/source/common/localematcher.cpp
@@ -131,6 +131,7 @@
thresholdDistance_(src.thresholdDistance_),
demotion_(src.demotion_),
defaultLocale_(src.defaultLocale_),
+ withDefault_(src.withDefault_),
favor_(src.favor_),
direction_(src.direction_) {
src.supportedLocales_ = nullptr;
@@ -150,6 +151,7 @@
thresholdDistance_ = src.thresholdDistance_;
demotion_ = src.demotion_;
defaultLocale_ = src.defaultLocale_;
+ withDefault_ = src.withDefault_,
favor_ = src.favor_;
direction_ = src.direction_;
@@ -229,6 +231,14 @@
return *this;
}
+LocaleMatcher::Builder &LocaleMatcher::Builder::setNoDefaultLocale() {
+ if (U_FAILURE(errorCode_)) { return *this; }
+ delete defaultLocale_;
+ defaultLocale_ = nullptr;
+ withDefault_ = false;
+ return *this;
+}
+
LocaleMatcher::Builder &LocaleMatcher::Builder::setDefaultLocale(const Locale *defaultLocale) {
if (U_FAILURE(errorCode_)) { return *this; }
Locale *clone = nullptr;
@@ -241,6 +251,7 @@
}
delete defaultLocale_;
defaultLocale_ = clone;
+ withDefault_ = true;
return *this;
}
@@ -417,13 +428,14 @@
for (int32_t i = 0; i < supportedLocalesLength; ++i) {
const Locale &locale = *supportedLocales[i];
const LSR &lsr = lsrs[i];
- if (defLSR == nullptr) {
+ if (defLSR == nullptr && builder.withDefault_) {
+ // Implicit default locale = first supported locale, if not turned off.
U_ASSERT(i == 0);
def = &locale;
defLSR = &lsr;
order[i] = 1;
suppLength = putIfAbsent(lsr, 0, suppLength, errorCode);
- } else if (lsr.isEquivalentTo(*defLSR)) {
+ } else if (defLSR != nullptr && lsr.isEquivalentTo(*defLSR)) {
order[i] = 1;
suppLength = putIfAbsent(lsr, i, suppLength, errorCode);
} else if (localeDistance.isParadigmLSR(lsr)) {
diff --git a/icu4c/source/common/unicode/localematcher.h b/icu4c/source/common/unicode/localematcher.h
index 2e1a7a3..3ec71df 100644
--- a/icu4c/source/common/unicode/localematcher.h
+++ b/icu4c/source/common/unicode/localematcher.h
@@ -231,8 +231,8 @@
/**
* Returns the best-matching supported locale.
* If none matched well enough, this is the default locale.
- * The default locale is nullptr if the list of supported locales is empty and
- * no explicit default locale is set.
+ * The default locale is nullptr if Builder::setNoDefaultLocale() was called,
+ * or if the list of supported locales is empty and no explicit default locale is set.
*
* @return the best-matching supported locale, or nullptr.
* @draft ICU 65
@@ -419,9 +419,23 @@
*/
Builder &addSupportedLocale(const Locale &locale);
+#ifndef U_HIDE_DRAFT_API
+ /**
+ * Sets no default locale.
+ * There will be no explicit or implicit default locale.
+ * If there is no good match, then the matcher will return nullptr for the
+ * best supported locale.
+ *
+ * @draft ICU 68
+ */
+ Builder &setNoDefaultLocale();
+#endif // U_HIDE_DRAFT_API
+
/**
* Sets the default locale; if nullptr, or if it is not set explicitly,
* then the first supported locale is used as the default locale.
+ * There is no default locale at all (nullptr will be returned instead)
+ * if setNoDefaultLocale() is called.
*
* @param defaultLocale the default locale (will be copied)
* @return this Builder object
@@ -505,6 +519,7 @@
int32_t thresholdDistance_ = -1;
ULocMatchDemotion demotion_ = ULOCMATCH_DEMOTION_REGION;
Locale *defaultLocale_ = nullptr;
+ bool withDefault_ = true;
ULocMatchFavorSubtag favor_ = ULOCMATCH_FAVOR_LANGUAGE;
ULocMatchDirection direction_ = ULOCMATCH_DIRECTION_WITH_ONE_WAY;
};
diff --git a/icu4c/source/test/intltest/localematchertest.cpp b/icu4c/source/test/intltest/localematchertest.cpp
index 683466b..62364ae 100644
--- a/icu4c/source/test/intltest/localematchertest.cpp
+++ b/icu4c/source/test/intltest/localematchertest.cpp
@@ -58,6 +58,7 @@
void testBasics();
void testSupportedDefault();
void testUnsupportedDefault();
+ void testNoDefault();
void testDemotion();
void testDirection();
void testMatch();
@@ -82,6 +83,7 @@
TESTCASE_AUTO(testBasics);
TESTCASE_AUTO(testSupportedDefault);
TESTCASE_AUTO(testUnsupportedDefault);
+ TESTCASE_AUTO(testNoDefault);
TESTCASE_AUTO(testDemotion);
TESTCASE_AUTO(testDirection);
TESTCASE_AUTO(testMatch);
@@ -302,6 +304,29 @@
-1, result.getSupportedIndex());
}
+void LocaleMatcherTest::testNoDefault() {
+ // We want nullptr instead of any default locale.
+ IcuTestErrorCode errorCode(*this, "testNoDefault");
+ Locale locales[] = { "fr", "en_GB", "en" };
+ LocaleMatcher matcher = LocaleMatcher::Builder().
+ setSupportedLocales(ARRAY_RANGE(locales)).
+ setNoDefaultLocale().
+ build(errorCode);
+ const Locale *best = matcher.getBestMatch("en_GB", errorCode);
+ assertEquals("getBestMatch(en_GB)", "en_GB", locString(best));
+ best = matcher.getBestMatch("en_US", errorCode);
+ assertEquals("getBestMatch(en_US)", "en", locString(best));
+ best = matcher.getBestMatch("fr_FR", errorCode);
+ assertEquals("getBestMatch(fr_FR)", "fr", locString(best));
+ best = matcher.getBestMatch("ja_JP", errorCode);
+ assertEquals("getBestMatch(ja_JP)", "(null)", locString(best));
+ LocaleMatcher::Result result = matcher.getBestMatchResult("ja_JP", errorCode);
+ assertEquals("getBestMatchResult(ja_JP).supp",
+ "(null)", locString(result.getSupportedLocale()));
+ assertEquals("getBestMatchResult(ja_JP).suppIndex",
+ -1, result.getSupportedIndex());
+}
+
void LocaleMatcherTest::testDemotion() {
IcuTestErrorCode errorCode(*this, "testDemotion");
Locale supported[] = { "fr", "de-CH", "it" };
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/LocaleMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/util/LocaleMatcher.java
index 73a9f4d..400090a 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/util/LocaleMatcher.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/util/LocaleMatcher.java
@@ -244,8 +244,8 @@
/**
* Returns the best-matching supported locale.
* If none matched well enough, this is the default locale.
- * The default locale is null if the list of supported locales is empty and
- * no explicit default locale is set.
+ * The default locale is null if {@link Builder#setNoDefaultLocale()} was called,
+ * or if the list of supported locales is empty and no explicit default locale is set.
*
* @return the best-matching supported locale, or null.
* @draft ICU 65
@@ -255,8 +255,8 @@
/**
* Returns the best-matching supported locale.
* If none matched well enough, this is the default locale.
- * The default locale is null if the list of supported locales is empty and
- * no explicit default locale is set.
+ * The default locale is null if {@link Builder#setNoDefaultLocale()} was called,
+ * or if the list of supported locales is empty and no explicit default locale is set.
*
* @return the best-matching supported locale, or null.
* @draft ICU 65
@@ -382,6 +382,7 @@
private int thresholdDistance = -1;
private Demotion demotion;
private ULocale defaultLocale;
+ private boolean withDefault = true;
private FavorSubtag favor;
private Direction direction;
@@ -465,8 +466,25 @@
}
/**
+ * Sets no default locale.
+ * There will be no explicit or implicit default locale.
+ * If there is no good match, then the matcher will return null for the
+ * best supported locale.
+ *
+ * @draft ICU 68
+ * @provisional This API might change or be removed in a future release.
+ */
+ public Builder setNoDefaultLocale() {
+ this.defaultLocale = null;
+ withDefault = false;
+ return this;
+ }
+
+ /**
* Sets the default locale; if null, or if it is not set explicitly,
* then the first supported locale is used as the default locale.
+ * There is no default locale at all (null will be returned instead)
+ * if {@link #setNoDefaultLocale()} is called.
*
* @param defaultLocale the default locale
* @return this Builder object
@@ -475,12 +493,15 @@
*/
public Builder setDefaultULocale(ULocale defaultLocale) {
this.defaultLocale = defaultLocale;
+ withDefault = true;
return this;
}
/**
* Sets the default locale; if null, or if it is not set explicitly,
* then the first supported locale is used as the default locale.
+ * There is no default locale at all (null will be returned instead)
+ * if {@link #setNoDefaultLocale()} is called.
*
* @param defaultLocale the default locale
* @return this Builder object
@@ -489,6 +510,7 @@
*/
public Builder setDefaultLocale(Locale defaultLocale) {
this.defaultLocale = ULocale.forLocale(defaultLocale);
+ withDefault = true;
return this;
}
@@ -673,13 +695,14 @@
i = 0;
for (ULocale locale : supportedULocales) {
LSR lsr = lsrs[i];
- if (defLSR == null) {
+ if (defLSR == null && builder.withDefault) {
+ // Implicit default locale = first supported locale, if not turned off.
assert i == 0;
udef = locale;
def = supportedLocales[0];
defLSR = lsr;
suppLength = putIfAbsent(lsr, 0, suppLength);
- } else if (lsr.isEquivalentTo(defLSR)) {
+ } else if (defLSR != null && lsr.isEquivalentTo(defLSR)) {
suppLength = putIfAbsent(lsr, i, suppLength);
} else if (LocaleDistance.INSTANCE.isParadigmLSR(lsr)) {
order[i] = 2;
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/LocaleMatcherTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/LocaleMatcherTest.java
index ff6e700..d4df833 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/LocaleMatcherTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/LocaleMatcherTest.java
@@ -222,6 +222,30 @@
}
@Test
+ public void testNoDefault() {
+ // We want null instead of any default locale.
+ List<ULocale> locales = Arrays.asList(
+ new ULocale("fr"), new ULocale("en_GB"), new ULocale("en"));
+ LocaleMatcher matcher = LocaleMatcher.builder().
+ setSupportedULocales(locales).
+ setNoDefaultLocale().
+ build();
+ ULocale best = matcher.getBestMatch("en_GB");
+ assertEquals("getBestMatch(en_GB)", "en_GB", locString(best));
+ best = matcher.getBestMatch("en_US");
+ assertEquals("getBestMatch(en_US)", "en", locString(best));
+ best = matcher.getBestMatch("fr_FR");
+ assertEquals("getBestMatch(fr_FR)", "fr", locString(best));
+ best = matcher.getBestMatch("ja_JP");
+ assertEquals("getBestMatch(ja_JP)", "(null)", locString(best));
+ LocaleMatcher.Result result = matcher.getBestMatchResult(new ULocale("ja_JP"));
+ assertEquals("getBestMatchResult(ja_JP).supp",
+ "(null)", locString(result.getSupportedULocale()));
+ assertEquals("getBestMatchResult(ja_JP).suppIndex",
+ -1, result.getSupportedIndex());
+ }
+
+ @Test
public void testFallback() {
// check that script fallbacks are handled right
final LocaleMatcher matcher = newLocaleMatcher("zh_CN, zh_TW, iw");