Extract logic for created header controlled resource bundles
This commit is contained in:
parent
f5a539e128
commit
94bf3a3902
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.i18n;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.ResourceBundle.Control;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
public class HeaderControlledResourceBundleLookup {
|
||||||
|
|
||||||
|
private static final int MAX_LOCALES = 15;
|
||||||
|
|
||||||
|
private final ResourceBundleFactory resourceBundleFactory;
|
||||||
|
|
||||||
|
public HeaderControlledResourceBundleLookup() {
|
||||||
|
this(ResourceBundle::getBundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public HeaderControlledResourceBundleLookup(
|
||||||
|
@Nonnull final ResourceBundleFactory resourceBundleFactory) {
|
||||||
|
this.resourceBundleFactory = Objects.requireNonNull(resourceBundleFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private List<Locale> getAcceptableLocales(final List<Locale> acceptableLanguages) {
|
||||||
|
return acceptableLanguages.stream().limit(MAX_LOCALES).distinct().collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public ResourceBundle getResourceBundle(final String baseName, final List<Locale> acceptableLocales) {
|
||||||
|
final List<Locale> deduplicatedLocales = getAcceptableLocales(acceptableLocales);
|
||||||
|
final Locale desiredLocale = deduplicatedLocales.isEmpty() ? Locale.getDefault() : deduplicatedLocales.get(0);
|
||||||
|
// define a control with a fallback order as specified in the header
|
||||||
|
Control control = new Control() {
|
||||||
|
@Override
|
||||||
|
public List<String> getFormats(final String baseName) {
|
||||||
|
Objects.requireNonNull(baseName);
|
||||||
|
return Control.FORMAT_PROPERTIES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Locale getFallbackLocale(final String baseName, final Locale locale) {
|
||||||
|
Objects.requireNonNull(baseName);
|
||||||
|
if (locale.equals(Locale.getDefault())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final int localeIndex = deduplicatedLocales.indexOf(locale);
|
||||||
|
if (localeIndex < 0 || localeIndex >= deduplicatedLocales.size() - 1) {
|
||||||
|
return Locale.getDefault();
|
||||||
|
}
|
||||||
|
// [0, deduplicatedLocales.size() - 2] is now the possible range for localeIndex
|
||||||
|
return deduplicatedLocales.get(localeIndex + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return resourceBundleFactory.createBundle(baseName, desiredLocale, control);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.badges;
|
package org.signal.i18n;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
|
@ -13,12 +13,10 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.ResourceBundle.Control;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javax.annotation.Nonnull;
|
import org.signal.i18n.HeaderControlledResourceBundleLookup;
|
||||||
import org.whispersystems.textsecuregcm.configuration.BadgeConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.BadgeConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.entities.Badge;
|
import org.whispersystems.textsecuregcm.entities.Badge;
|
||||||
|
@ -28,37 +26,36 @@ import org.whispersystems.textsecuregcm.storage.AccountBadge;
|
||||||
|
|
||||||
public class ConfiguredProfileBadgeConverter implements ProfileBadgeConverter, BadgeTranslator {
|
public class ConfiguredProfileBadgeConverter implements ProfileBadgeConverter, BadgeTranslator {
|
||||||
|
|
||||||
private static final int MAX_LOCALES = 15;
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final String BASE_NAME = "org.signal.badges.Badges";
|
static final String BASE_NAME = "org.signal.badges.Badges";
|
||||||
|
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final Map<String, BadgeConfiguration> knownBadges;
|
private final Map<String, BadgeConfiguration> knownBadges;
|
||||||
private final List<String> badgeIdsEnabledForAll;
|
private final List<String> badgeIdsEnabledForAll;
|
||||||
private final ResourceBundleFactory resourceBundleFactory;
|
private final HeaderControlledResourceBundleLookup headerControlledResourceBundleLookup;
|
||||||
|
|
||||||
public ConfiguredProfileBadgeConverter(
|
public ConfiguredProfileBadgeConverter(
|
||||||
final Clock clock,
|
final Clock clock,
|
||||||
final BadgesConfiguration badgesConfiguration) {
|
final BadgesConfiguration badgesConfiguration) {
|
||||||
this(clock, badgesConfiguration, ResourceBundle::getBundle);
|
this(clock, badgesConfiguration, new HeaderControlledResourceBundleLookup());
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public ConfiguredProfileBadgeConverter(
|
public ConfiguredProfileBadgeConverter(
|
||||||
final Clock clock,
|
final Clock clock,
|
||||||
final BadgesConfiguration badgesConfiguration,
|
final BadgesConfiguration badgesConfiguration,
|
||||||
final ResourceBundleFactory resourceBundleFactory) {
|
final HeaderControlledResourceBundleLookup headerControlledResourceBundleLookup) {
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.knownBadges = badgesConfiguration.getBadges().stream()
|
this.knownBadges = badgesConfiguration.getBadges().stream()
|
||||||
.collect(Collectors.toMap(BadgeConfiguration::getId, Function.identity()));
|
.collect(Collectors.toMap(BadgeConfiguration::getId, Function.identity()));
|
||||||
this.badgeIdsEnabledForAll = badgesConfiguration.getBadgeIdsEnabledForAll();
|
this.badgeIdsEnabledForAll = badgesConfiguration.getBadgeIdsEnabledForAll();
|
||||||
this.resourceBundleFactory = resourceBundleFactory;
|
this.headerControlledResourceBundleLookup = headerControlledResourceBundleLookup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Badge translate(final List<Locale> acceptableLanguages, final String badgeId) {
|
public Badge translate(final List<Locale> acceptableLanguages, final String badgeId) {
|
||||||
final List<Locale> acceptableLocales = getAcceptableLocales(acceptableLanguages);
|
final ResourceBundle resourceBundle = headerControlledResourceBundleLookup.getResourceBundle(BASE_NAME,
|
||||||
final ResourceBundle resourceBundle = getResourceBundle(acceptableLocales);
|
acceptableLanguages);
|
||||||
final BadgeConfiguration configuration = knownBadges.get(badgeId);
|
final BadgeConfiguration configuration = knownBadges.get(badgeId);
|
||||||
return newBadge(
|
return newBadge(
|
||||||
false,
|
false,
|
||||||
|
@ -83,8 +80,8 @@ public class ConfiguredProfileBadgeConverter implements ProfileBadgeConverter, B
|
||||||
}
|
}
|
||||||
|
|
||||||
final Instant now = clock.instant();
|
final Instant now = clock.instant();
|
||||||
final List<Locale> acceptableLocales = getAcceptableLocales(acceptableLanguages);
|
final ResourceBundle resourceBundle = headerControlledResourceBundleLookup.getResourceBundle(BASE_NAME,
|
||||||
final ResourceBundle resourceBundle = getResourceBundle(acceptableLocales);
|
acceptableLanguages);
|
||||||
List<Badge> badges = accountBadges.stream()
|
List<Badge> badges = accountBadges.stream()
|
||||||
.filter(accountBadge -> (isSelf || accountBadge.isVisible())
|
.filter(accountBadge -> (isSelf || accountBadge.isVisible())
|
||||||
&& now.isBefore(accountBadge.getExpiration())
|
&& now.isBefore(accountBadge.getExpiration())
|
||||||
|
@ -121,40 +118,6 @@ public class ConfiguredProfileBadgeConverter implements ProfileBadgeConverter, B
|
||||||
return badges;
|
return badges;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
private ResourceBundle getResourceBundle(final List<Locale> acceptableLocales) {
|
|
||||||
final Locale desiredLocale = acceptableLocales.isEmpty() ? Locale.getDefault() : acceptableLocales.get(0);
|
|
||||||
// define a control with a fallback order as specified in the header
|
|
||||||
Control control = new Control() {
|
|
||||||
@Override
|
|
||||||
public List<String> getFormats(final String baseName) {
|
|
||||||
Objects.requireNonNull(baseName);
|
|
||||||
return Control.FORMAT_PROPERTIES;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Locale getFallbackLocale(final String baseName, final Locale locale) {
|
|
||||||
Objects.requireNonNull(baseName);
|
|
||||||
if (locale.equals(Locale.getDefault())) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final int localeIndex = acceptableLocales.indexOf(locale);
|
|
||||||
if (localeIndex < 0 || localeIndex >= acceptableLocales.size() - 1) {
|
|
||||||
return Locale.getDefault();
|
|
||||||
}
|
|
||||||
// [0, acceptableLocales.size() - 2] is now the possible range for localeIndex
|
|
||||||
return acceptableLocales.get(localeIndex + 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return resourceBundleFactory.createBundle(BASE_NAME, desiredLocale, control);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
private List<Locale> getAcceptableLocales(final List<Locale> acceptableLanguages) {
|
|
||||||
return acceptableLanguages.stream().limit(MAX_LOCALES).distinct().collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Badge newBadge(
|
private Badge newBadge(
|
||||||
final boolean isSelf,
|
final boolean isSelf,
|
||||||
final String id,
|
final String id,
|
||||||
|
|
|
@ -29,6 +29,8 @@ import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.signal.i18n.HeaderControlledResourceBundleLookup;
|
||||||
|
import org.signal.i18n.ResourceBundleFactory;
|
||||||
import org.whispersystems.textsecuregcm.configuration.BadgeConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.BadgeConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.entities.Badge;
|
import org.whispersystems.textsecuregcm.entities.Badge;
|
||||||
|
@ -66,7 +68,8 @@ public class ConfiguredProfileBadgeConverterTest {
|
||||||
|
|
||||||
private static BadgeConfiguration newBadge(int i) {
|
private static BadgeConfiguration newBadge(int i) {
|
||||||
return new BadgeConfiguration(
|
return new BadgeConfiguration(
|
||||||
idFor(i), "other", List.of("l", "m", "h", "x", "xx", "xxx"), "SVG", List.of(new BadgeSvg("sl", "sd", "st"), new BadgeSvg("ml", "md", "mt"), new BadgeSvg("ll", "ld", "lt")));
|
idFor(i), "other", List.of("l", "m", "h", "x", "xx", "xxx"), "SVG",
|
||||||
|
List.of(new BadgeSvg("sl", "sd", "st"), new BadgeSvg("ml", "md", "mt"), new BadgeSvg("ll", "ld", "lt")));
|
||||||
}
|
}
|
||||||
|
|
||||||
private BadgesConfiguration createBadges(int count) {
|
private BadgesConfiguration createBadges(int count) {
|
||||||
|
@ -104,7 +107,7 @@ public class ConfiguredProfileBadgeConverterTest {
|
||||||
void testConvertEmptyList() {
|
void testConvertEmptyList() {
|
||||||
BadgesConfiguration badgesConfiguration = createBadges(1);
|
BadgesConfiguration badgesConfiguration = createBadges(1);
|
||||||
ConfiguredProfileBadgeConverter badgeConverter = new ConfiguredProfileBadgeConverter(clock, badgesConfiguration,
|
ConfiguredProfileBadgeConverter badgeConverter = new ConfiguredProfileBadgeConverter(clock, badgesConfiguration,
|
||||||
resourceBundleFactory);
|
new HeaderControlledResourceBundleLookup(resourceBundleFactory));
|
||||||
assertThat(badgeConverter.convert(List.of(Locale.getDefault()), List.of(), false)).isNotNull().isEmpty();
|
assertThat(badgeConverter.convert(List.of(Locale.getDefault()), List.of(), false)).isNotNull().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +116,8 @@ public class ConfiguredProfileBadgeConverterTest {
|
||||||
void testNoLocales(String name, Instant expiration, boolean visible, boolean isSelf, Badge expectedBadge) {
|
void testNoLocales(String name, Instant expiration, boolean visible, boolean isSelf, Badge expectedBadge) {
|
||||||
BadgesConfiguration badgesConfiguration = createBadges(1);
|
BadgesConfiguration badgesConfiguration = createBadges(1);
|
||||||
ConfiguredProfileBadgeConverter badgeConverter =
|
ConfiguredProfileBadgeConverter badgeConverter =
|
||||||
new ConfiguredProfileBadgeConverter(clock, badgesConfiguration, resourceBundleFactory);
|
new ConfiguredProfileBadgeConverter(clock, badgesConfiguration,
|
||||||
|
new HeaderControlledResourceBundleLookup(resourceBundleFactory));
|
||||||
setupResourceBundle(Locale.getDefault());
|
setupResourceBundle(Locale.getDefault());
|
||||||
|
|
||||||
if (expectedBadge != null) {
|
if (expectedBadge != null) {
|
||||||
|
@ -136,15 +140,26 @@ public class ConfiguredProfileBadgeConverterTest {
|
||||||
arguments(idFor(0), expired, false, false, null),
|
arguments(idFor(0), expired, false, false, null),
|
||||||
arguments(idFor(0), notExpired, false, false, null),
|
arguments(idFor(0), notExpired, false, false, null),
|
||||||
arguments(idFor(0), expired, true, false, null),
|
arguments(idFor(0), expired, true, false, null),
|
||||||
arguments(idFor(0), notExpired, true, false, new Badge(idFor(0), "other", nameFor(0), desriptionFor(0), List.of("l", "m", "h", "x", "xx", "xxx"), "SVG", List.of(new BadgeSvg("sl", "sd", "st"), new BadgeSvg("ml", "md", "mt"), new BadgeSvg("ll", "ld", "lt")))),
|
arguments(idFor(0), notExpired, true, false,
|
||||||
|
new Badge(idFor(0), "other", nameFor(0), desriptionFor(0), List.of("l", "m", "h", "x", "xx", "xxx"), "SVG",
|
||||||
|
List.of(new BadgeSvg("sl", "sd", "st"), new BadgeSvg("ml", "md", "mt"),
|
||||||
|
new BadgeSvg("ll", "ld", "lt")))),
|
||||||
arguments(idFor(1), expired, false, false, null),
|
arguments(idFor(1), expired, false, false, null),
|
||||||
arguments(idFor(1), notExpired, false, false, null),
|
arguments(idFor(1), notExpired, false, false, null),
|
||||||
arguments(idFor(1), expired, true, false, null),
|
arguments(idFor(1), expired, true, false, null),
|
||||||
arguments(idFor(1), notExpired, true, false, null),
|
arguments(idFor(1), notExpired, true, false, null),
|
||||||
arguments(idFor(0), expired, false, true, null),
|
arguments(idFor(0), expired, false, true, null),
|
||||||
arguments(idFor(0), notExpired, false, true, new SelfBadge(idFor(0), "other", nameFor(0), desriptionFor(0), List.of("l", "m", "h", "x", "xx", "xxx"), "SVG", List.of(new BadgeSvg("sl", "sd", "st"), new BadgeSvg("ml", "md", "mt"), new BadgeSvg("ll", "ld", "lt")), notExpired, false)),
|
arguments(idFor(0), notExpired, false, true,
|
||||||
|
new SelfBadge(idFor(0), "other", nameFor(0), desriptionFor(0), List.of("l", "m", "h", "x", "xx", "xxx"),
|
||||||
|
"SVG",
|
||||||
|
List.of(new BadgeSvg("sl", "sd", "st"), new BadgeSvg("ml", "md", "mt"), new BadgeSvg("ll", "ld", "lt")),
|
||||||
|
notExpired, false)),
|
||||||
arguments(idFor(0), expired, true, true, null),
|
arguments(idFor(0), expired, true, true, null),
|
||||||
arguments(idFor(0), notExpired, true, true, new SelfBadge(idFor(0), "other", nameFor(0), desriptionFor(0), List.of("l", "m", "h", "x", "xx", "xxx"), "SVG", List.of(new BadgeSvg("sl", "sd", "st"), new BadgeSvg("ml", "md", "mt"), new BadgeSvg("ll", "ld", "lt")), notExpired, true)),
|
arguments(idFor(0), notExpired, true, true,
|
||||||
|
new SelfBadge(idFor(0), "other", nameFor(0), desriptionFor(0), List.of("l", "m", "h", "x", "xx", "xxx"),
|
||||||
|
"SVG",
|
||||||
|
List.of(new BadgeSvg("sl", "sd", "st"), new BadgeSvg("ml", "md", "mt"), new BadgeSvg("ll", "ld", "lt")),
|
||||||
|
notExpired, true)),
|
||||||
arguments(idFor(1), expired, false, true, null),
|
arguments(idFor(1), expired, false, true, null),
|
||||||
arguments(idFor(1), notExpired, false, true, null),
|
arguments(idFor(1), notExpired, false, true, null),
|
||||||
arguments(idFor(1), expired, true, true, null),
|
arguments(idFor(1), expired, true, true, null),
|
||||||
|
@ -155,7 +170,8 @@ public class ConfiguredProfileBadgeConverterTest {
|
||||||
void testCustomControl() {
|
void testCustomControl() {
|
||||||
BadgesConfiguration badgesConfiguration = createBadges(1);
|
BadgesConfiguration badgesConfiguration = createBadges(1);
|
||||||
ConfiguredProfileBadgeConverter badgeConverter =
|
ConfiguredProfileBadgeConverter badgeConverter =
|
||||||
new ConfiguredProfileBadgeConverter(clock, badgesConfiguration, resourceBundleFactory);
|
new ConfiguredProfileBadgeConverter(clock, badgesConfiguration,
|
||||||
|
new HeaderControlledResourceBundleLookup(resourceBundleFactory));
|
||||||
|
|
||||||
Locale defaultLocale = Locale.getDefault();
|
Locale defaultLocale = Locale.getDefault();
|
||||||
Locale enGb = new Locale("en", "GB");
|
Locale enGb = new Locale("en", "GB");
|
||||||
|
|
Loading…
Reference in New Issue