ICU-20936 add LocaleMatcher.Builder.setDirection(with-one-way vs. only-two-way)
diff --git a/icu4c/source/common/localematcher.cpp b/icu4c/source/common/localematcher.cpp
index f5f3a62..2cce3f4 100644
--- a/icu4c/source/common/localematcher.cpp
+++ b/icu4c/source/common/localematcher.cpp
@@ -131,7 +131,8 @@
thresholdDistance_(src.thresholdDistance_),
demotion_(src.demotion_),
defaultLocale_(src.defaultLocale_),
- favor_(src.favor_) {
+ favor_(src.favor_),
+ direction_(src.direction_) {
src.supportedLocales_ = nullptr;
src.defaultLocale_ = nullptr;
}
@@ -150,6 +151,7 @@
demotion_ = src.demotion_;
defaultLocale_ = src.defaultLocale_;
favor_ = src.favor_;
+ direction_ = src.direction_;
src.supportedLocales_ = nullptr;
src.defaultLocale_ = nullptr;
@@ -332,6 +334,7 @@
thresholdDistance(builder.thresholdDistance_),
demotionPerDesiredLocale(0),
favorSubtag(builder.favor_),
+ direction(builder.direction_),
supportedLocales(nullptr), lsrs(nullptr), supportedLocalesLength(0),
supportedLsrToIndex(nullptr),
supportedLSRs(nullptr), supportedIndexes(nullptr), supportedLSRsLength(0),
@@ -649,7 +652,8 @@
}
}
int32_t bestIndexAndDistance = localeDistance.getBestIndexAndDistance(
- desiredLSR, supportedLSRs, supportedLSRsLength, bestShiftedDistance, favorSubtag);
+ desiredLSR, supportedLSRs, supportedLSRsLength,
+ bestShiftedDistance, favorSubtag, direction);
if (bestIndexAndDistance >= 0) {
bestShiftedDistance = LocaleDistance::getShiftedDistance(bestIndexAndDistance);
if (remainingIter != nullptr) {
@@ -683,7 +687,7 @@
int32_t indexAndDistance = localeDistance.getBestIndexAndDistance(
getMaximalLsrOrUnd(likelySubtags, desired, errorCode),
&pSuppLSR, 1,
- LocaleDistance::shiftDistance(thresholdDistance), favorSubtag);
+ LocaleDistance::shiftDistance(thresholdDistance), favorSubtag, direction);
double distance = LocaleDistance::getDistanceDouble(indexAndDistance);
return (100.0 - distance) / 100.0;
}
diff --git a/icu4c/source/common/locdistance.cpp b/icu4c/source/common/locdistance.cpp
index 4304fab..26d3ff7 100644
--- a/icu4c/source/common/locdistance.cpp
+++ b/icu4c/source/common/locdistance.cpp
@@ -102,14 +102,15 @@
LSR enGB("en", "Latn", "GB", LSR::EXPLICIT_LSR);
const LSR *p_enGB = &enGB;
int32_t indexAndDistance = getBestIndexAndDistance(en, &p_enGB, 1,
- shiftDistance(50), ULOCMATCH_FAVOR_LANGUAGE);
+ shiftDistance(50), ULOCMATCH_FAVOR_LANGUAGE, ULOCMATCH_DIRECTION_WITH_ONE_WAY);
defaultDemotionPerDesiredLocale = getDistanceFloor(indexAndDistance);
}
int32_t LocaleDistance::getBestIndexAndDistance(
const LSR &desired,
const LSR **supportedLSRs, int32_t supportedLSRsLength,
- int32_t shiftedThreshold, ULocMatchFavorSubtag favorSubtag) const {
+ int32_t shiftedThreshold,
+ ULocMatchFavorSubtag favorSubtag, ULocMatchDirection direction) const {
// Round up the shifted threshold (if fraction bits are not 0)
// for comparison with un-shifted distances until we need fraction bits.
// (If we simply shifted non-zero fraction bits away, then we might ignore a language
@@ -211,26 +212,38 @@
// additional micro distance.
shiftedDistance |= (desired.flags ^ supported.flags);
if (shiftedDistance < shiftedThreshold) {
- if (shiftedDistance == 0) {
- return slIndex << INDEX_SHIFT;
+ if (direction != ULOCMATCH_DIRECTION_ONLY_TWO_WAY ||
+ // Is there also a match when we swap desired/supported?
+ isMatch(supported, desired, shiftedThreshold, favorSubtag)) {
+ if (shiftedDistance == 0) {
+ return slIndex << INDEX_SHIFT;
+ }
+ bestIndex = slIndex;
+ shiftedThreshold = shiftedDistance;
+ bestLikelyInfo = -1;
}
- bestIndex = slIndex;
- shiftedThreshold = shiftedDistance;
- bestLikelyInfo = -1;
}
} else {
if (shiftedDistance < shiftedThreshold) {
- bestIndex = slIndex;
- shiftedThreshold = shiftedDistance;
- bestLikelyInfo = -1;
- } else if (shiftedDistance == shiftedThreshold && bestIndex >= 0) {
- bestLikelyInfo = likelySubtags.compareLikely(
- supported, *supportedLSRs[bestIndex], bestLikelyInfo);
- if ((bestLikelyInfo & 1) != 0) {
- // This supported locale matches as well as the previous best match,
- // and neither matches perfectly,
- // but this one is "more likely" (has more-default subtags).
+ if (direction != ULOCMATCH_DIRECTION_ONLY_TWO_WAY ||
+ // Is there also a match when we swap desired/supported?
+ isMatch(supported, desired, shiftedThreshold, favorSubtag)) {
bestIndex = slIndex;
+ shiftedThreshold = shiftedDistance;
+ bestLikelyInfo = -1;
+ }
+ } else if (shiftedDistance == shiftedThreshold && bestIndex >= 0) {
+ if (direction != ULOCMATCH_DIRECTION_ONLY_TWO_WAY ||
+ // Is there also a match when we swap desired/supported?
+ isMatch(supported, desired, shiftedThreshold, favorSubtag)) {
+ bestLikelyInfo = likelySubtags.compareLikely(
+ supported, *supportedLSRs[bestIndex], bestLikelyInfo);
+ if ((bestLikelyInfo & 1) != 0) {
+ // This supported locale matches as well as the previous best match,
+ // and neither matches perfectly,
+ // but this one is "more likely" (has more-default subtags).
+ bestIndex = slIndex;
+ }
}
}
}
diff --git a/icu4c/source/common/locdistance.h b/icu4c/source/common/locdistance.h
index 88fd73f..ad84151 100644
--- a/icu4c/source/common/locdistance.h
+++ b/icu4c/source/common/locdistance.h
@@ -55,7 +55,8 @@
int32_t getBestIndexAndDistance(const LSR &desired,
const LSR **supportedLSRs, int32_t supportedLSRsLength,
int32_t shiftedThreshold,
- ULocMatchFavorSubtag favorSubtag) const;
+ ULocMatchFavorSubtag favorSubtag,
+ ULocMatchDirection direction) const;
UBool isParadigmLSR(const LSR &lsr) const;
@@ -88,6 +89,14 @@
static void initLocaleDistance(UErrorCode &errorCode);
+ UBool isMatch(const LSR &desired, const LSR &supported,
+ int32_t shiftedThreshold, ULocMatchFavorSubtag favorSubtag) const {
+ const LSR *pSupp = &supported;
+ return getBestIndexAndDistance(
+ desired, &pSupp, 1,
+ shiftedThreshold, favorSubtag, ULOCMATCH_DIRECTION_WITH_ONE_WAY) >= 0;
+ }
+
static int32_t getDesSuppScriptDistance(BytesTrie &iter, uint64_t startState,
const char *desired, const char *supported);
diff --git a/icu4c/source/common/unicode/localematcher.h b/icu4c/source/common/unicode/localematcher.h
index 1bab23e..8e79aaf 100644
--- a/icu4c/source/common/unicode/localematcher.h
+++ b/icu4c/source/common/unicode/localematcher.h
@@ -25,7 +25,7 @@
/**
* Builder option for whether the language subtag or the script subtag is most important.
*
- * @see Builder#setFavorSubtag(FavorSubtag)
+ * @see Builder#setFavorSubtag(ULocMatchFavorSubtag)
* @draft ICU 65
*/
enum ULocMatchFavorSubtag {
@@ -51,7 +51,7 @@
* Builder option for whether all desired locales are treated equally or
* earlier ones are preferred.
*
- * @see Builder#setDemotionPerDesiredLocale(Demotion)
+ * @see Builder#setDemotionPerDesiredLocale(ULocMatchDemotion)
* @draft ICU 65
*/
enum ULocMatchDemotion {
@@ -93,6 +93,42 @@
typedef enum ULocMatchDemotion ULocMatchDemotion;
#endif
+/**
+ * Builder option for whether to include or ignore one-way (fallback) match data.
+ * The LocaleMatcher uses CLDR languageMatch data which includes fallback (oneway=true) entries.
+ * Sometimes it is desirable to ignore those.
+ *
+ * <p>For example, consider a web application with the UI in a given language,
+ * with a link to another, related web app.
+ * The link should include the UI language, and the target server may also use
+ * the client’s Accept-Language header data.
+ * The target server has its own list of supported languages.
+ * One may want to favor UI language consistency, that is,
+ * if there is a decent match for the original UI language, we want to use it,
+ * but not if it is merely a fallback.
+ *
+ * @see Builder#setDirection(ULocMatchDirection)
+ * @draft ICU 67
+ */
+enum ULocMatchDirection {
+ /**
+ * Locale matching includes one-way matches such as Breton→French. (default)
+ *
+ * @draft ICU 67
+ */
+ ULOCMATCH_DIRECTION_WITH_ONE_WAY,
+ /**
+ * Locale matching limited to two-way matches including e.g. Danish↔Norwegian
+ * but ignoring one-way matches.
+ *
+ * @draft ICU 67
+ */
+ ULOCMATCH_DIRECTION_ONLY_TWO_WAY
+};
+#ifndef U_IN_DOXYGEN
+typedef enum ULocMatchDirection ULocMatchDirection;
+#endif
+
struct UHashtable;
U_NAMESPACE_BEGIN
@@ -413,6 +449,21 @@
Builder &setDemotionPerDesiredLocale(ULocMatchDemotion demotion);
/**
+ * Option for whether to include or ignore one-way (fallback) match data.
+ * By default, they are included.
+ *
+ * @param direction the match direction to set.
+ * @return this Builder object
+ * @draft ICU 67
+ */
+ Builder &setDirection(ULocMatchDirection direction) {
+ if (U_SUCCESS(errorCode_)) {
+ direction_ = direction;
+ }
+ return *this;
+ }
+
+ /**
* Sets the UErrorCode if an error occurred while setting parameters.
* Preserves older error codes in the outErrorCode.
*
@@ -451,6 +502,7 @@
ULocMatchDemotion demotion_ = ULOCMATCH_DEMOTION_REGION;
Locale *defaultLocale_ = nullptr;
ULocMatchFavorSubtag favor_ = ULOCMATCH_FAVOR_LANGUAGE;
+ ULocMatchDirection direction_ = ULOCMATCH_DIRECTION_WITH_ONE_WAY;
};
// FYI No public LocaleMatcher constructors in C++; use the Builder.
@@ -583,6 +635,7 @@
int32_t thresholdDistance;
int32_t demotionPerDesiredLocale;
ULocMatchFavorSubtag favorSubtag;
+ ULocMatchDirection direction;
// These are in input order.
const Locale ** supportedLocales;
diff --git a/icu4c/source/test/intltest/localematchertest.cpp b/icu4c/source/test/intltest/localematchertest.cpp
index 6027807..6d7f48d 100644
--- a/icu4c/source/test/intltest/localematchertest.cpp
+++ b/icu4c/source/test/intltest/localematchertest.cpp
@@ -58,6 +58,7 @@
void testSupportedDefault();
void testUnsupportedDefault();
void testDemotion();
+ void testDirection();
void testMatch();
void testResolvedLocale();
void testDataDriven();
@@ -81,6 +82,7 @@
TESTCASE_AUTO(testSupportedDefault);
TESTCASE_AUTO(testUnsupportedDefault);
TESTCASE_AUTO(testDemotion);
+ TESTCASE_AUTO(testDirection);
TESTCASE_AUTO(testMatch);
TESTCASE_AUTO(testResolvedLocale);
TESTCASE_AUTO(testDataDriven);
@@ -322,6 +324,31 @@
}
}
+void LocaleMatcherTest::testDirection() {
+ IcuTestErrorCode errorCode(*this, "testDirection");
+ Locale supported[] = { "ar", "nn" };
+ Locale desired[] = { "arz-EG", "nb-DK" };
+ LocaleMatcher::Builder builder;
+ builder.setSupportedLocales(ARRAY_RANGE(supported));
+ {
+ // arz is a close one-way match to ar, and the region matches.
+ // (Egyptian Arabic vs. Arabic)
+ LocaleMatcher withOneWay = builder.build(errorCode);
+ Locale::RangeIterator<Locale *> desiredIter(ARRAY_RANGE(desired));
+ assertEquals("with one-way", "ar",
+ locString(withOneWay.getBestMatch(desiredIter, errorCode)));
+ }
+ {
+ // nb is a less close two-way match to nn, and the regions differ.
+ // (Norwegian Bokmal vs. Nynorsk)
+ LocaleMatcher onlyTwoWay =
+ builder.setDirection(ULOCMATCH_DIRECTION_ONLY_TWO_WAY).build(errorCode);
+ Locale::RangeIterator<Locale *> desiredIter(ARRAY_RANGE(desired));
+ assertEquals("only two-way", "nn",
+ locString(onlyTwoWay.getBestMatch(desiredIter, errorCode)));
+ }
+}
+
void LocaleMatcherTest::testMatch() {
IcuTestErrorCode errorCode(*this, "testMatch");
LocaleMatcher matcher = LocaleMatcher::Builder().build(errorCode);
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/locale/LocaleDistance.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/locale/LocaleDistance.java
index 3d785c2..796f4e9 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/locale/LocaleDistance.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/locale/LocaleDistance.java
@@ -15,6 +15,7 @@
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.UResource;
import com.ibm.icu.util.BytesTrie;
+import com.ibm.icu.util.LocaleMatcher;
import com.ibm.icu.util.LocaleMatcher.FavorSubtag;
import com.ibm.icu.util.ULocale;
@@ -211,7 +212,7 @@
LSR en = new LSR("en", "Latn", "US", LSR.EXPLICIT_LSR);
LSR enGB = new LSR("en", "Latn", "GB", LSR.EXPLICIT_LSR);
int indexAndDistance = getBestIndexAndDistance(en, new LSR[] { enGB }, 1,
- shiftDistance(50), FavorSubtag.LANGUAGE);
+ shiftDistance(50), FavorSubtag.LANGUAGE, LocaleMatcher.Direction.WITH_ONE_WAY);
defaultDemotionPerDesiredLocale = getDistanceFloor(indexAndDistance);
if (DEBUG_OUTPUT) {
@@ -229,7 +230,7 @@
LSR supportedLSR = XLikelySubtags.INSTANCE.makeMaximizedLsrFrom(supported);
LSR desiredLSR = XLikelySubtags.INSTANCE.makeMaximizedLsrFrom(desired);
int indexAndDistance = getBestIndexAndDistance(desiredLSR, new LSR[] { supportedLSR }, 1,
- shiftDistance(threshold), favorSubtag);
+ shiftDistance(threshold), favorSubtag, LocaleMatcher.Direction.WITH_ONE_WAY);
return getDistanceFloor(indexAndDistance);
}
@@ -242,7 +243,7 @@
* and its distance (0..ABOVE_THRESHOLD) in the low bits.
*/
public int getBestIndexAndDistance(LSR desired, LSR[] supportedLSRs, int supportedLSRsLength,
- int shiftedThreshold, FavorSubtag favorSubtag) {
+ int shiftedThreshold, FavorSubtag favorSubtag, LocaleMatcher.Direction direction) {
// Round up the shifted threshold (if fraction bits are not 0)
// for comparison with un-shifted distances until we need fraction bits.
// (If we simply shifted non-zero fraction bits away, then we might ignore a language
@@ -344,26 +345,38 @@
// additional micro distance.
shiftedDistance |= (desired.flags ^ supported.flags);
if (shiftedDistance < shiftedThreshold) {
- if (shiftedDistance == 0) {
- return slIndex << INDEX_SHIFT;
+ if (direction != LocaleMatcher.Direction.ONLY_TWO_WAY ||
+ // Is there also a match when we swap desired/supported?
+ isMatch(supported, desired, shiftedThreshold, favorSubtag)) {
+ if (shiftedDistance == 0) {
+ return slIndex << INDEX_SHIFT;
+ }
+ bestIndex = slIndex;
+ shiftedThreshold = shiftedDistance;
+ bestLikelyInfo = -1;
}
- bestIndex = slIndex;
- shiftedThreshold = shiftedDistance;
- bestLikelyInfo = -1;
}
} else {
if (shiftedDistance < shiftedThreshold) {
- bestIndex = slIndex;
- shiftedThreshold = shiftedDistance;
- bestLikelyInfo = -1;
- } else if (shiftedDistance == shiftedThreshold && bestIndex >= 0) {
- bestLikelyInfo = XLikelySubtags.INSTANCE.compareLikely(
- supported, supportedLSRs[bestIndex], bestLikelyInfo);
- if ((bestLikelyInfo & 1) != 0) {
- // This supported locale matches as well as the previous best match,
- // and neither matches perfectly,
- // but this one is "more likely" (has more-default subtags).
+ if (direction != LocaleMatcher.Direction.ONLY_TWO_WAY ||
+ // Is there also a match when we swap desired/supported?
+ isMatch(supported, desired, shiftedThreshold, favorSubtag)) {
bestIndex = slIndex;
+ shiftedThreshold = shiftedDistance;
+ bestLikelyInfo = -1;
+ }
+ } else if (shiftedDistance == shiftedThreshold && bestIndex >= 0) {
+ if (direction != LocaleMatcher.Direction.ONLY_TWO_WAY ||
+ // Is there also a match when we swap desired/supported?
+ isMatch(supported, desired, shiftedThreshold, favorSubtag)) {
+ bestLikelyInfo = XLikelySubtags.INSTANCE.compareLikely(
+ supported, supportedLSRs[bestIndex], bestLikelyInfo);
+ if ((bestLikelyInfo & 1) != 0) {
+ // This supported locale matches as well as the previous best match,
+ // and neither matches perfectly,
+ // but this one is "more likely" (has more-default subtags).
+ bestIndex = slIndex;
+ }
}
}
}
@@ -373,6 +386,13 @@
INDEX_NEG_1 | shiftDistance(ABOVE_THRESHOLD);
}
+ private boolean isMatch(LSR desired, LSR supported,
+ int shiftedThreshold, FavorSubtag favorSubtag) {
+ return getBestIndexAndDistance(
+ desired, new LSR[] { supported }, 1,
+ shiftedThreshold, favorSubtag, null) >= 0;
+ }
+
private static final int getDesSuppScriptDistance(BytesTrie iter, long startState,
String desired, String supported) {
// Note: The data builder verifies that there are no <*, supported> or <desired, *> rules.
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 e96b647..df029d2 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
@@ -156,6 +156,42 @@
}
/**
+ * Builder option for whether to include or ignore one-way (fallback) match data.
+ * The LocaleMatcher uses CLDR languageMatch data which includes fallback (oneway=true) entries.
+ * Sometimes it is desirable to ignore those.
+ *
+ * <p>For example, consider a web application with the UI in a given language,
+ * with a link to another, related web app.
+ * The link should include the UI language, and the target server may also use
+ * the client’s Accept-Language header data.
+ * The target server has its own list of supported languages.
+ * One may want to favor UI language consistency, that is,
+ * if there is a decent match for the original UI language, we want to use it,
+ * but not if it is merely a fallback.
+ *
+ * @see LocaleMatcher.Builder#setDirection(Direction)
+ * @draft ICU 67
+ * @provisional This API might change or be removed in a future release.
+ */
+ public enum Direction {
+ /**
+ * Locale matching includes one-way matches such as Breton→French. (default)
+ *
+ * @draft ICU 67
+ * @provisional This API might change or be removed in a future release.
+ */
+ WITH_ONE_WAY,
+ /**
+ * Locale matching limited to two-way matches including e.g. Danish↔Norwegian
+ * but ignoring one-way matches.
+ *
+ * @draft ICU 67
+ * @provisional This API might change or be removed in a future release.
+ */
+ ONLY_TWO_WAY
+ }
+
+ /**
* Data for the best-matching pair of a desired and a supported locale.
*
* @draft ICU 65
@@ -319,6 +355,7 @@
private final int thresholdDistance;
private final int demotionPerDesiredLocale;
private final FavorSubtag favorSubtag;
+ private final Direction direction;
// These are in input order.
private final ULocale[] supportedULocales;
@@ -346,6 +383,7 @@
private Demotion demotion;
private ULocale defaultLocale;
private FavorSubtag favor;
+ private Direction direction;
private Builder() {}
@@ -484,6 +522,20 @@
}
/**
+ * Option for whether to include or ignore one-way (fallback) match data.
+ * By default, they are included.
+ *
+ * @param direction the match direction to set.
+ * @return this Builder object
+ * @draft ICU 67
+ * @provisional This API might change or be removed in a future release.
+ */
+ public Builder setDirection(Direction direction) {
+ this.direction = direction;
+ return this;
+ }
+
+ /**
* <i>Internal only!</i>
*
* @param thresholdDistance the thresholdDistance to set, with -1 = default
@@ -661,6 +713,7 @@
builder.demotion == Demotion.NONE ? 0 :
LocaleDistance.INSTANCE.getDefaultDemotionPerDesiredLocale(); // null or REGION
favorSubtag = builder.favor;
+ direction = builder.direction;
if (TRACE_MATCHER) {
System.err.printf("new LocaleMatcher: %s\n", toString());
}
@@ -945,7 +998,7 @@
}
int bestIndexAndDistance = LocaleDistance.INSTANCE.getBestIndexAndDistance(
desiredLSR, supportedLSRs, supportedLSRsLength,
- bestShiftedDistance, favorSubtag);
+ bestShiftedDistance, favorSubtag, direction);
if (bestIndexAndDistance >= 0) {
bestShiftedDistance = LocaleDistance.getShiftedDistance(bestIndexAndDistance);
if (remainingIter != null) { remainingIter.rememberCurrent(desiredIndex); }
@@ -998,7 +1051,7 @@
int indexAndDistance = LocaleDistance.INSTANCE.getBestIndexAndDistance(
getMaximalLsrOrUnd(desired),
new LSR[] { getMaximalLsrOrUnd(supported) }, 1,
- LocaleDistance.shiftDistance(thresholdDistance), favorSubtag);
+ LocaleDistance.shiftDistance(thresholdDistance), favorSubtag, direction);
double distance = LocaleDistance.getDistanceDouble(indexAndDistance);
if (TRACE_MATCHER) {
System.err.printf("LocaleMatcher distance(desired=%s, supported=%s)=%g\n",
@@ -1044,6 +1097,9 @@
if (favorSubtag != null) {
s.append(" favor=").append(favorSubtag);
}
+ if (direction != null) {
+ s.append(" direction=").append(direction);
+ }
if (thresholdDistance >= 0) {
s.append(String.format(" threshold=%d", thresholdDistance));
}
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 194403f..ff6e700 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
@@ -639,6 +639,21 @@
}
@Test
+ public void testDirection() {
+ List<ULocale> desired = Arrays.asList(new ULocale("arz-EG"), new ULocale("nb-DK"));
+ LocaleMatcher.Builder builder =
+ LocaleMatcher.builder().setSupportedLocales("ar, nn");
+ // arz is a close one-way match to ar, and the region matches.
+ // (Egyptian Arabic vs. Arabic)
+ LocaleMatcher withOneWay = builder.build();
+ assertEquals("with one-way", "ar", withOneWay.getBestMatch(desired).toString());
+ // nb is a less close two-way match to nn, and the regions differ.
+ // (Norwegian Bokmal vs. Nynorsk)
+ LocaleMatcher onlyTwoWay = builder.setDirection(LocaleMatcher.Direction.ONLY_TWO_WAY).build();
+ assertEquals("only two-way", "nn", onlyTwoWay.getBestMatch(desired).toString());
+ }
+
+ @Test
public void testCanonicalize() {
LocaleMatcher matcher = LocaleMatcher.builder().build();
assertEquals("bh --> bho", new ULocale("bho"), matcher.canonicalize(new ULocale("bh")));