ICU-21029 LocaleMatcher: add option to turn off default locale

This commit is contained in:
Markus Scherer 2020-05-19 19:17:44 -07:00
parent b6eb747550
commit eaee0b175e
5 changed files with 109 additions and 10 deletions

View file

@ -131,6 +131,7 @@ LocaleMatcher::Builder::Builder(LocaleMatcher::Builder &&src) U_NOEXCEPT :
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 @@ LocaleMatcher::Builder &LocaleMatcher::Builder::operator=(LocaleMatcher::Builder
thresholdDistance_ = src.thresholdDistance_;
demotion_ = src.demotion_;
defaultLocale_ = src.defaultLocale_;
withDefault_ = src.withDefault_,
favor_ = src.favor_;
direction_ = src.direction_;
@ -229,6 +231,14 @@ LocaleMatcher::Builder &LocaleMatcher::Builder::addSupportedLocale(const Locale
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 @@ LocaleMatcher::Builder &LocaleMatcher::Builder::setDefaultLocale(const Locale *d
}
delete defaultLocale_;
defaultLocale_ = clone;
withDefault_ = true;
return *this;
}
@ -417,13 +428,14 @@ LocaleMatcher::LocaleMatcher(const Builder &builder, UErrorCode &errorCode) :
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)) {

View file

@ -231,8 +231,8 @@ public:
/**
* 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 @@ public:
*/
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 @@ public:
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;
};

View file

@ -58,6 +58,7 @@ public:
void testBasics();
void testSupportedDefault();
void testUnsupportedDefault();
void testNoDefault();
void testDemotion();
void testDirection();
void testMatch();
@ -82,6 +83,7 @@ void LocaleMatcherTest::runIndexedTest(int32_t index, UBool exec, const char *&n
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 @@ void LocaleMatcherTest::testUnsupportedDefault() {
-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" };

View file

@ -244,8 +244,8 @@ public final class LocaleMatcher {
/**
* 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 @@ public final class LocaleMatcher {
/**
* 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 @@ public final class LocaleMatcher {
private int thresholdDistance = -1;
private Demotion demotion;
private ULocale defaultLocale;
private boolean withDefault = true;
private FavorSubtag favor;
private Direction direction;
@ -464,9 +465,26 @@ public final class LocaleMatcher {
return addSupportedULocale(ULocale.forLocale(locale));
}
/**
* 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 final class LocaleMatcher {
*/
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 final class LocaleMatcher {
*/
public Builder setDefaultLocale(Locale defaultLocale) {
this.defaultLocale = ULocale.forLocale(defaultLocale);
withDefault = true;
return this;
}
@ -673,13 +695,14 @@ public final class LocaleMatcher {
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;

View file

@ -221,6 +221,30 @@ public class LocaleMatcherTest extends TestFmwk {
-1, result.getSupportedIndex());
}
@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