From c95e16ed192a6c2362080be056807b08eb0d1aff Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 18 May 2026 21:07:05 +0200 Subject: [PATCH 1/4] feat: Add requiredLocaleAffinity to LocalesResolver --- .../common/impl/LocalesResolverBaseImpl.java | 48 ++++- ...ocaleAffinityBiCalculatorBaseImplTest.java | 5 +- .../LocaleAffinityCalculatorBaseImplTest.java | 20 ++- .../impl/LocalesResolverBaseImplTest.java | 170 +++++++++++++++++- 4 files changed, 231 insertions(+), 12 deletions(-) diff --git a/locales-common/src/main/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImpl.java b/locales-common/src/main/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImpl.java index ec1f983..becb55b 100644 --- a/locales-common/src/main/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImpl.java +++ b/locales-common/src/main/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImpl.java @@ -20,11 +20,18 @@ package com.spotify.i18n.locales.common.impl; +import static com.spotify.i18n.locales.common.model.LocaleAffinity.HIGH; +import static com.spotify.i18n.locales.common.model.LocaleAffinity.LOW; +import static com.spotify.i18n.locales.common.model.LocaleAffinity.MUTUALLY_INTELLIGIBLE; +import static com.spotify.i18n.locales.common.model.LocaleAffinity.SAME; + import com.google.auto.value.AutoValue; +import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.ibm.icu.util.LocaleMatcher; import com.ibm.icu.util.ULocale; import com.spotify.i18n.locales.common.LocalesResolver; +import com.spotify.i18n.locales.common.model.LocaleAffinity; import com.spotify.i18n.locales.common.model.ResolvedLocale; import com.spotify.i18n.locales.common.model.SupportedLocale; import com.spotify.i18n.locales.utils.acceptlanguage.AcceptLanguageUtils; @@ -32,6 +39,7 @@ import com.spotify.i18n.locales.utils.hierarchy.LocalesHierarchyUtils; import com.spotify.i18n.locales.utils.languagetag.LanguageTagUtils; import java.util.List; +import java.util.Locale; import java.util.Locale.LanguageRange; import java.util.Map; import java.util.Optional; @@ -68,12 +76,25 @@ public abstract class LocalesResolverBaseImpl implements LocalesResolver { .map(ULocale::getLanguage) .collect(Collectors.toSet()); + /** Default required locale affinity for resolved locale */ + public static final LocaleAffinity DEFAULT_REQUIRED_LOCALE_AFFINITY = MUTUALLY_INTELLIGIBLE; + /** Wildcard character for language ranges */ private static final String LANGUAGE_RANGE_WILDCARD = "*"; /** Parseable code for undefined value in a language tag */ private static final String ULOCALE_UNDEFINED_CODE = "Und"; + /** + * Affinity to distance threshold for {@link LocaleMatcher.Builder#setMaxDistance(Locale, Locale)} + */ + private static final Map AFFINITY_TO_DISTANCE_THRESHOLD = + Map.of( + SAME, 6, + MUTUALLY_INTELLIGIBLE, 10, + HIGH, 20, + LOW, 100); + /** * Returns the set of {@link SupportedLocale} against which locale resolution will be performed. * @@ -81,6 +102,13 @@ public abstract class LocalesResolverBaseImpl implements LocalesResolver { */ public abstract Set supportedLocales(); + /** + * Returns the required {@link LocaleAffinity} for the resolved locale + * + * @return The required {@link LocaleAffinity} + */ + public abstract LocaleAffinity requiredLocaleAffinity(); + /** * Returns the default {@link ResolvedLocale} that will be used as final fallback for a given * Accept-Language value when no match can be found, or when the resolution fails for some reason. @@ -442,6 +470,7 @@ private ULocale getLocaleForFormatting( private LocaleMatcher getLocaleMatcher(final Set supportedLocales) { return LocaleMatcher.builder() .setSupportedULocales(supportedLocales) + .internalSetThresholdDistance(AFFINITY_TO_DISTANCE_THRESHOLD.get(requiredLocaleAffinity())) .setNoDefaultLocale() .build(); } @@ -459,7 +488,9 @@ public static Builder builder() { /** A builder for a {@link LocalesResolverBaseImpl}. */ @AutoValue.Builder public abstract static class Builder { - Builder() {} // package private constructor + Builder() { // package private constructor + requiredLocaleAffinity(DEFAULT_REQUIRED_LOCALE_AFFINITY); + } /** * Configures the default {@link ResolvedLocale} that will be returned if locale resolution @@ -479,11 +510,24 @@ public abstract static class Builder { */ public abstract Builder supportedLocales(final Set supportedLocales); + /** + * Configures the set of {@link LocaleAffinity} based on which locale resolution will be + * performed. + * + * @param requiredLocaleAffinity + * @return The {@link Builder} + */ + public abstract Builder requiredLocaleAffinity(final LocaleAffinity requiredLocaleAffinity); + abstract LocalesResolverBaseImpl autoBuild(); /** Builds a {@link LocalesResolver} out of this builder. */ public final LocalesResolver build() { - return autoBuild(); + LocalesResolverBaseImpl built = autoBuild(); + Preconditions.checkState( + built.requiredLocaleAffinity() != LocaleAffinity.NONE, + "Required locale affinity for resolved locale cannot be NONE."); + return built; } } } diff --git a/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocaleAffinityBiCalculatorBaseImplTest.java b/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocaleAffinityBiCalculatorBaseImplTest.java index 002374e..b10b6c2 100644 --- a/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocaleAffinityBiCalculatorBaseImplTest.java +++ b/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocaleAffinityBiCalculatorBaseImplTest.java @@ -146,7 +146,10 @@ public static Stream whenCalculating_returnsExpectedAffinity() { Arguments.of("zh-Hant", "zh", NONE), Arguments.of("zh-MK", "zh-CN", SAME), Arguments.of("zh-FR", "zh-CN", SAME), - Arguments.of("zh-TW", "zh-US", SAME)); + Arguments.of("zh-TW", "zh-US", SAME), + + // Malay + Arguments.of("ms", "id", HIGH)); } @Test diff --git a/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocaleAffinityCalculatorBaseImplTest.java b/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocaleAffinityCalculatorBaseImplTest.java index 043d8d2..67319a1 100644 --- a/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocaleAffinityCalculatorBaseImplTest.java +++ b/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocaleAffinityCalculatorBaseImplTest.java @@ -20,6 +20,7 @@ package com.spotify.i18n.locales.common.impl; +import static com.spotify.i18n.locales.common.model.LocaleAffinity.HIGH; import static com.spotify.i18n.locales.common.model.LocaleAffinity.LOW; import static com.spotify.i18n.locales.common.model.LocaleAffinity.MUTUALLY_INTELLIGIBLE; import static com.spotify.i18n.locales.common.model.LocaleAffinity.NONE; @@ -50,7 +51,10 @@ class LocaleAffinityCalculatorBaseImplTest { public static final LocaleAffinityCalculator CALCULATOR_AGAINST_TEST_SET_OF_LOCALES = LocaleAffinityCalculatorBaseImpl.builder() .againstLocales( - Set.of("ar", "bs-Cyrl", "es", "fr", "ja", "pt", "sr-Latn", "zh-Hant").stream() + Set.of( + "ar", "bs-Cyrl", "es", "fr", "id", "ja", "nb", "pt", "ru", "sr-Latn", + "zh-Hant") + .stream() .map(ULocale::forLanguageTag) .collect(Collectors.toSet())) .build(); @@ -186,6 +190,9 @@ public static Stream whenCalculating_returnsExpectedAffinity() { // Basque should be matched, since we support Spanish Arguments.of("eu", LOW), + // Danish + Arguments.of("da", HIGH), + // French Arguments.of("fr", SAME), Arguments.of("fr-BE", SAME), @@ -206,6 +213,17 @@ public static Stream whenCalculating_returnsExpectedAffinity() { Arguments.of("sr-Latn", SAME), Arguments.of("sr-Cyrl-ME", SAME), + // Kazakh should be matched with Russian + Arguments.of("kk", NONE), + Arguments.of("kk-KZ", NONE), + + // Malay + Arguments.of("ms", HIGH), + + // Mongolian should be matched with Russian + Arguments.of("mn", NONE), + Arguments.of("mn-MN", NONE), + // Portuguese Arguments.of("pt", SAME), Arguments.of("pt-BR", SAME), diff --git a/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImplTest.java b/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImplTest.java index 8b74a8e..9a6c21b 100644 --- a/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImplTest.java +++ b/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImplTest.java @@ -20,12 +20,20 @@ package com.spotify.i18n.locales.common.impl; +import static com.spotify.i18n.locales.common.model.LocaleAffinity.HIGH; +import static com.spotify.i18n.locales.common.model.LocaleAffinity.LOW; +import static com.spotify.i18n.locales.common.model.LocaleAffinity.MUTUALLY_INTELLIGIBLE; +import static com.spotify.i18n.locales.common.model.LocaleAffinity.SAME; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.ibm.icu.impl.locale.LocaleDistance; +import com.ibm.icu.util.LocaleMatcher.FavorSubtag; +import com.ibm.icu.util.ULocale; import com.spotify.i18n.locales.common.LocalesResolver; +import com.spotify.i18n.locales.common.model.LocaleAffinity; import com.spotify.i18n.locales.common.model.ResolvedLocale; import com.spotify.i18n.locales.common.model.SupportedLocale; import java.util.Collections; @@ -33,6 +41,7 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -173,7 +182,7 @@ public void whenResolvingUnsupportedLocale_returnBetterDefaultLocale( static Stream whenResolvingUnsupportedLocale_returnBetterDefaultLocale() { Set supportedLocales = // We add 3 different variants of English to the list of supported locales - Set.of("en", "en-GB", "en-IN", "hi", "fr", "ar", "zh-Hant", "ja").stream() + Set.of("ar", "en", "en-GB", "en-IN", "fr", "hi", "hr", "id", "ja", "nb", "zh-Hant").stream() .map(SupportedLocale::fromLanguageTag) .collect(Collectors.toSet()); @@ -199,7 +208,10 @@ static Stream whenResolvingUnsupportedLocale_returnBetterDefaultLocal supportedLocales, ResolvedLocale.fromLanguageTags("en-IN", List.of("en"), "en-IN")), // Tamil (Malaysia) - Arguments.of("ta-MY", supportedLocales, ResolvedLocale.fromLanguageTags("en", "en-MY")), + Arguments.of( + "ta-MY", + supportedLocales, + ResolvedLocale.fromLanguageTags("en-GB", List.of("en"), "en-MY")), // Nama (Namibia) Arguments.of( "naq-NA", @@ -209,7 +221,19 @@ static Stream whenResolvingUnsupportedLocale_returnBetterDefaultLocal Arguments.of( "dz-BT", supportedLocales, - ResolvedLocale.fromLanguageTags("en-GB", List.of("en"), "en-GB"))); + ResolvedLocale.fromLanguageTags("en-GB", List.of("en"), "en-GB")), + // Bosnian (Bosnia) + Arguments.of("bs-BA", supportedLocales, ResolvedLocale.fromLanguageTags("hr", "hr-BA")), + // Danish (Danemark), should NOT be matched with nb (Norwegian Bokmål) + Arguments.of( + "da-DK", + supportedLocales, + ResolvedLocale.fromLanguageTags("en-GB", List.of("en"), "en-DK")), + // Malay (Malaysia), should NOT be matched with id (Indonesian) + Arguments.of( + "ms-MY", + supportedLocales, + ResolvedLocale.fromLanguageTags("en-GB", List.of("en"), "en-MY"))); } private static final Set SUPPORTED_LOCALES = @@ -304,6 +328,7 @@ public void whenResolvingEdgeCaseLocales_returnsExpectedLocale( LocalesResolverBaseImpl.builder() .supportedLocales(SUPPORTED_LOCALES) .defaultResolvedLocale(DEFAULT_LOCALE) + .requiredLocaleAffinity(MUTUALLY_INTELLIGIBLE) .build(); ResolvedLocale expectedResolvedLocale = @@ -318,13 +343,14 @@ static Stream whenResolvingEdgeCaseLocales_returnsExpectedLocale() { return Stream.of( Arguments.of("en-LK", "en-GB", List.of("en"), "en-GB"), Arguments.of("es-419", "es-419", List.of("es"), "es-419"), - Arguments.of("ga-IE", "en", Collections.emptyList(), "en-IE"), + Arguments.of("ga-IE", "en-GB", List.of("en"), "en-IE"), Arguments.of("gn-PY", "es-419", List.of("es"), "es-PY"), - Arguments.of("kk-KZ", "ru", Collections.emptyList(), "ru-KZ"), - Arguments.of("mn-MN", "ru", Collections.emptyList(), "ru-RU"), - Arguments.of("nso-ZA", "en", Collections.emptyList(), "en-ZA"), - Arguments.of("sq-XK", "en", Collections.emptyList(), "en-US"), + Arguments.of("kk-KZ", "en-GB", List.of("en"), "en-GB"), + Arguments.of("mn-MN", "en-GB", List.of("en"), "en-GB"), + Arguments.of("nso-ZA", "en-GB", List.of("en"), "en-ZA"), + Arguments.of("sq-XK", "en-GB", List.of("en"), "en-GB"), Arguments.of("sr-Latn-XK", "sr-Latn", Collections.emptyList(), "sr-Latn-XK"), + Arguments.of("sr-Cyrl-XK", "sr-Latn", Collections.emptyList(), "sr-Latn-XK"), Arguments.of("sr-XK", "sr-Latn", Collections.emptyList(), "sr-Latn-XK")); } @@ -349,4 +375,132 @@ public void whenCldrAncestorLocalesAreUnsupported_theyAreNotPresentAsFallbacks() "es-419"); assertThat(resolver.resolve(localeToResolve), is(expectedResolvedLocale)); } + + @ParameterizedTest + @MethodSource + public void givenRequiredLocaleAffinity_whenResolvingLocale_returnsExpected( + final LocaleAffinity requiredAffinity, String input, ResolvedLocale resolvedLocale) { + final Set supportedLocales = + Set.of("es", "es-419", "hr", "nb", "id", "ru", "sr-Latn").stream() + .map(SupportedLocale::fromLanguageTag) + .collect(Collectors.toSet()); + + final LocalesResolver resolver = + LocalesResolverBaseImpl.builder() + .supportedLocales(supportedLocales) + .defaultResolvedLocale(DEFAULT_LOCALE) + .requiredLocaleAffinity(requiredAffinity) + .build(); + + assertEquals(resolvedLocale, resolver.resolve(input)); + } + + static Stream givenRequiredLocaleAffinity_whenResolvingLocale_returnsExpected() { + Set supportedLocales = + Set.of("es", "es-419", "hr", "nb", "id", "ru", "sr-Latn").stream() + .map(SupportedLocale::fromLanguageTag) + .collect(Collectors.toSet()); + + return Stream.of( + // SAME affinity + Arguments.of(SAME, "bs", DEFAULT_LOCALE), // Bosnian should not resolve to Croatian + Arguments.of(SAME, "da", DEFAULT_LOCALE), // Danish should not resolve to Norwegian Bokmål + Arguments.of(SAME, "ms", DEFAULT_LOCALE), // Malay should not resolve to Indonesian + Arguments.of(SAME, "kk", DEFAULT_LOCALE), // Kazakh should not resolve to Russian + Arguments.of(SAME, "mn", DEFAULT_LOCALE), // Mongolian should not resolve to Russian + Arguments.of( + SAME, + "es-PY", + ResolvedLocale.fromLanguageTags( + "es-419", List.of("es"), "es-PY")), // Spanish (Paraguay) should resolve to + Arguments.of( + SAME, + "es-PT", + ResolvedLocale.fromLanguageTags("es", "es-ES")), // Spanish (Paraguay) should resolve to + Arguments.of( + SAME, + "sr-Cyrl-RS", + ResolvedLocale.fromLanguageTags( + "sr-Latn", "sr-Latn")), // Serbian (Cyrillic) should resolve to Serbian (Latin) + + // Mutually Intelligible + Arguments.of(MUTUALLY_INTELLIGIBLE, "bs", ResolvedLocale.fromLanguageTags("hr", "hr-BA")), + Arguments.of( + MUTUALLY_INTELLIGIBLE, + "da", + DEFAULT_LOCALE), // Danish should not resolve to Norwegian Bokmål + Arguments.of( + MUTUALLY_INTELLIGIBLE, "ms", DEFAULT_LOCALE), // Malay should not resolve to Indonesian + Arguments.of( + MUTUALLY_INTELLIGIBLE, "kk", DEFAULT_LOCALE), // Kazakh should not resolve to Russian + Arguments.of( + MUTUALLY_INTELLIGIBLE, "mn", DEFAULT_LOCALE), // Mongolian should not resolve to Russian + Arguments.of(MUTUALLY_INTELLIGIBLE, "eu-ES", DEFAULT_LOCALE), + Arguments.of(MUTUALLY_INTELLIGIBLE, "ca-AD", DEFAULT_LOCALE), + // Spanish (Paraguay) should resolve to Spanish (Latin America) + Arguments.of( + MUTUALLY_INTELLIGIBLE, + "es-PY", + ResolvedLocale.fromLanguageTags("es-419", List.of("es"), "es-PY")), + // Spanish (Portugal) should resolve to Spanish + Arguments.of( + MUTUALLY_INTELLIGIBLE, "es-PT", ResolvedLocale.fromLanguageTags("es", "es-ES")), + // Serbian (Cyrillic) should resolve to Serbian (Latin) + Arguments.of( + MUTUALLY_INTELLIGIBLE, + "sr-Cyrl-RS", + ResolvedLocale.fromLanguageTags("sr-Latn", "sr-Latn")), + + // HIGH affinity + Arguments.of(HIGH, "da", ResolvedLocale.fromLanguageTags("nb", "nb-NO")), + Arguments.of(HIGH, "da-DK", ResolvedLocale.fromLanguageTags("nb", "nb-NO")), + + // ⚠️ Logic for Malay differs from LocaleAffinityBiCalculatorBaseImpl, it should resolve to + // Indonesian. This should be fixed somehow, but is considered as acceptable for now. + Arguments.of(HIGH, "ms", DEFAULT_LOCALE), + Arguments.of(HIGH, "ms-MY", DEFAULT_LOCALE), + Arguments.of(HIGH, "mn-KZ", DEFAULT_LOCALE), + Arguments.of(HIGH, "mn", DEFAULT_LOCALE), + Arguments.of(HIGH, "eu-ES", DEFAULT_LOCALE), + Arguments.of(HIGH, "ca-AD", DEFAULT_LOCALE), + + // LOW affinity + Arguments.of(LOW, "da", ResolvedLocale.fromLanguageTags("nb", "nb-NO")), + Arguments.of(LOW, "da-DK", ResolvedLocale.fromLanguageTags("nb", "nb-NO")), + Arguments.of(LOW, "ms", ResolvedLocale.fromLanguageTags("id", "id-ID")), + Arguments.of(LOW, "ms-MY", ResolvedLocale.fromLanguageTags("id", "id-ID")), + Arguments.of(LOW, "mn-KZ", ResolvedLocale.fromLanguageTags("ru", "ru-KZ")), + Arguments.of(LOW, "mn", ResolvedLocale.fromLanguageTags("ru", "ru-RU")), + Arguments.of(LOW, "eu-ES", ResolvedLocale.fromLanguageTags("es", "es-ES")), + Arguments.of(LOW, "ca-AD", ResolvedLocale.fromLanguageTags("es", "es-ES"))); + } + + @Test + @Disabled + public void testDistance() { + System.out.println( + LocaleDistance.INSTANCE.testOnlyDistance( + ULocale.forLanguageTag("eu-ES"), + ULocale.forLanguageTag("es"), + LocaleDistance.INSTANCE.getDefaultScriptDistance(), + FavorSubtag.LANGUAGE)); + System.out.println( + LocaleDistance.INSTANCE.testOnlyDistance( + ULocale.forLanguageTag("ca-AD"), + ULocale.forLanguageTag("es"), + LocaleDistance.INSTANCE.getDefaultScriptDistance(), + FavorSubtag.LANGUAGE)); + System.out.println( + LocaleDistance.INSTANCE.testOnlyDistance( + ULocale.forLanguageTag("da"), + ULocale.forLanguageTag("nb"), + LocaleDistance.INSTANCE.getDefaultScriptDistance(), + FavorSubtag.LANGUAGE)); + System.out.println( + LocaleDistance.INSTANCE.testOnlyDistance( + ULocale.forLanguageTag("ms"), + ULocale.forLanguageTag("id"), + LocaleDistance.INSTANCE.getDefaultScriptDistance(), + FavorSubtag.LANGUAGE)); + } } From 9c8256c25a10d6975c7f0e09bdcd4ebf96f3c68e Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 19 May 2026 17:18:22 +0200 Subject: [PATCH 2/4] test: Ensure affinity NONE triggers exception --- .../impl/LocalesResolverBaseImplTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImplTest.java b/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImplTest.java index 9a6c21b..067512a 100644 --- a/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImplTest.java +++ b/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImplTest.java @@ -23,6 +23,7 @@ import static com.spotify.i18n.locales.common.model.LocaleAffinity.HIGH; import static com.spotify.i18n.locales.common.model.LocaleAffinity.LOW; import static com.spotify.i18n.locales.common.model.LocaleAffinity.MUTUALLY_INTELLIGIBLE; +import static com.spotify.i18n.locales.common.model.LocaleAffinity.NONE; import static com.spotify.i18n.locales.common.model.LocaleAffinity.SAME; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -60,6 +61,23 @@ void whenBuildingWithMissingRequiredProperties_buildFails() { thrown.getMessage(), "Missing required properties: supportedLocales defaultResolvedLocale"); } + + @Test + void whenBuildingWithRequiredLocaleAffinityNONE_buildFails() { + IllegalStateException thrown = + assertThrows(IllegalStateException.class, () -> LocalesResolverBaseImpl.builder() + .supportedLocales( + Set.of("fr", "ar", "zh-Hant", "ja").stream() + .map(SupportedLocale::fromLanguageTag) + .collect(Collectors.toSet())) + .defaultResolvedLocale(DEFAULT_LOCALE) + .requiredLocaleAffinity(NONE) + .build()); + + assertEquals( + thrown.getMessage(), "Required locale affinity for resolved locale cannot be NONE."); + } + @Test void whenBuildingWithAllRequiredProperties_handlesInvalidAndEdgeCasesProperly() { LocalesResolver resolver = From dd672c6127360bc99432b884d1a5c03eb3ba4c25 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 19 May 2026 19:26:51 +0200 Subject: [PATCH 3/4] chore: rename requiredLocaleAffinity to minimumLocaleAffinity --- .../common/impl/LocalesResolverBaseImpl.java | 14 ++++++++------ .../common/impl/LocalesResolverBaseImplTest.java | 12 ++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/locales-common/src/main/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImpl.java b/locales-common/src/main/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImpl.java index becb55b..f56e240 100644 --- a/locales-common/src/main/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImpl.java +++ b/locales-common/src/main/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImpl.java @@ -107,7 +107,7 @@ public abstract class LocalesResolverBaseImpl implements LocalesResolver { * * @return The required {@link LocaleAffinity} */ - public abstract LocaleAffinity requiredLocaleAffinity(); + public abstract LocaleAffinity minimumLocaleAffinity(); /** * Returns the default {@link ResolvedLocale} that will be used as final fallback for a given @@ -470,7 +470,7 @@ private ULocale getLocaleForFormatting( private LocaleMatcher getLocaleMatcher(final Set supportedLocales) { return LocaleMatcher.builder() .setSupportedULocales(supportedLocales) - .internalSetThresholdDistance(AFFINITY_TO_DISTANCE_THRESHOLD.get(requiredLocaleAffinity())) + .internalSetThresholdDistance(AFFINITY_TO_DISTANCE_THRESHOLD.get(minimumLocaleAffinity())) .setNoDefaultLocale() .build(); } @@ -489,7 +489,7 @@ public static Builder builder() { @AutoValue.Builder public abstract static class Builder { Builder() { // package private constructor - requiredLocaleAffinity(DEFAULT_REQUIRED_LOCALE_AFFINITY); + minimumLocaleAffinity(DEFAULT_REQUIRED_LOCALE_AFFINITY); } /** @@ -514,10 +514,12 @@ public abstract static class Builder { * Configures the set of {@link LocaleAffinity} based on which locale resolution will be * performed. * - * @param requiredLocaleAffinity + *

Default value: {@link LocaleAffinity#MUTUALLY_INTELLIGIBLE} + * + * @param minimumLocaleAffinity * @return The {@link Builder} */ - public abstract Builder requiredLocaleAffinity(final LocaleAffinity requiredLocaleAffinity); + public abstract Builder minimumLocaleAffinity(final LocaleAffinity minimumLocaleAffinity); abstract LocalesResolverBaseImpl autoBuild(); @@ -525,7 +527,7 @@ public abstract static class Builder { public final LocalesResolver build() { LocalesResolverBaseImpl built = autoBuild(); Preconditions.checkState( - built.requiredLocaleAffinity() != LocaleAffinity.NONE, + built.minimumLocaleAffinity() != LocaleAffinity.NONE, "Required locale affinity for resolved locale cannot be NONE."); return built; } diff --git a/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImplTest.java b/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImplTest.java index 067512a..94fbc73 100644 --- a/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImplTest.java +++ b/locales-common/src/test/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImplTest.java @@ -63,7 +63,7 @@ void whenBuildingWithMissingRequiredProperties_buildFails() { @Test - void whenBuildingWithRequiredLocaleAffinityNONE_buildFails() { + void whenBuildingWithMinimumLocaleAffinityNONE_buildFails() { IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> LocalesResolverBaseImpl.builder() .supportedLocales( @@ -71,7 +71,7 @@ void whenBuildingWithRequiredLocaleAffinityNONE_buildFails() { .map(SupportedLocale::fromLanguageTag) .collect(Collectors.toSet())) .defaultResolvedLocale(DEFAULT_LOCALE) - .requiredLocaleAffinity(NONE) + .minimumLocaleAffinity(NONE) .build()); assertEquals( @@ -346,7 +346,7 @@ public void whenResolvingEdgeCaseLocales_returnsExpectedLocale( LocalesResolverBaseImpl.builder() .supportedLocales(SUPPORTED_LOCALES) .defaultResolvedLocale(DEFAULT_LOCALE) - .requiredLocaleAffinity(MUTUALLY_INTELLIGIBLE) + .minimumLocaleAffinity(MUTUALLY_INTELLIGIBLE) .build(); ResolvedLocale expectedResolvedLocale = @@ -396,7 +396,7 @@ public void whenCldrAncestorLocalesAreUnsupported_theyAreNotPresentAsFallbacks() @ParameterizedTest @MethodSource - public void givenRequiredLocaleAffinity_whenResolvingLocale_returnsExpected( + public void givenMinimumLocaleAffinity_whenResolvingLocale_returnsExpected( final LocaleAffinity requiredAffinity, String input, ResolvedLocale resolvedLocale) { final Set supportedLocales = Set.of("es", "es-419", "hr", "nb", "id", "ru", "sr-Latn").stream() @@ -407,13 +407,13 @@ public void givenRequiredLocaleAffinity_whenResolvingLocale_returnsExpected( LocalesResolverBaseImpl.builder() .supportedLocales(supportedLocales) .defaultResolvedLocale(DEFAULT_LOCALE) - .requiredLocaleAffinity(requiredAffinity) + .minimumLocaleAffinity(requiredAffinity) .build(); assertEquals(resolvedLocale, resolver.resolve(input)); } - static Stream givenRequiredLocaleAffinity_whenResolvingLocale_returnsExpected() { + static Stream givenMinimumLocaleAffinity_whenResolvingLocale_returnsExpected() { Set supportedLocales = Set.of("es", "es-419", "hr", "nb", "id", "ru", "sr-Latn").stream() .map(SupportedLocale::fromLanguageTag) From e8479eef3a0326b3b81ecad40f512e260ba15216 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 20 May 2026 09:53:20 +0200 Subject: [PATCH 4/4] chore: Make minimumLocaleAffinity package private --- .../i18n/locales/common/impl/LocalesResolverBaseImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales-common/src/main/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImpl.java b/locales-common/src/main/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImpl.java index f56e240..917c53b 100644 --- a/locales-common/src/main/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImpl.java +++ b/locales-common/src/main/java/com/spotify/i18n/locales/common/impl/LocalesResolverBaseImpl.java @@ -107,7 +107,7 @@ public abstract class LocalesResolverBaseImpl implements LocalesResolver { * * @return The required {@link LocaleAffinity} */ - public abstract LocaleAffinity minimumLocaleAffinity(); + abstract LocaleAffinity minimumLocaleAffinity(); /** * Returns the default {@link ResolvedLocale} that will be used as final fallback for a given @@ -519,7 +519,7 @@ public abstract static class Builder { * @param minimumLocaleAffinity * @return The {@link Builder} */ - public abstract Builder minimumLocaleAffinity(final LocaleAffinity minimumLocaleAffinity); + abstract Builder minimumLocaleAffinity(final LocaleAffinity minimumLocaleAffinity); abstract LocalesResolverBaseImpl autoBuild();