diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index c9fbbb51a..25f31683f 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -52,10 +52,10 @@ import java.util.concurrent.TimeUnit; import javax.servlet.DispatcherType; import javax.servlet.FilterRegistration; import javax.servlet.ServletRegistration; -import javax.ws.rs.ext.ExceptionMapper; import org.eclipse.jetty.servlets.CrossOriginFilter; import org.glassfish.jersey.server.ServerProperties; import org.jdbi.v3.core.Jdbi; +import org.signal.i18n.HeaderControlledResourceBundleLookup; import org.signal.zkgroup.ServerSecretParams; import org.signal.zkgroup.auth.ServerZkAuthOperations; import org.signal.zkgroup.profiles.ServerZkProfileOperations; @@ -73,6 +73,7 @@ import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator; import org.whispersystems.textsecuregcm.auth.WebsocketRefreshApplicationEventListener; import org.whispersystems.textsecuregcm.badges.ConfiguredProfileBadgeConverter; +import org.whispersystems.textsecuregcm.badges.ResourceBundleLevelTranslator; import org.whispersystems.textsecuregcm.configuration.DirectoryServerConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.controllers.AccountController; @@ -299,8 +300,12 @@ public class WhisperServerService extends Application badgeIdsEnabledForAll; private final HeaderControlledResourceBundleLookup headerControlledResourceBundleLookup; - public ConfiguredProfileBadgeConverter( - final Clock clock, - final BadgesConfiguration badgesConfiguration) { - this(clock, badgesConfiguration, new HeaderControlledResourceBundleLookup()); - } - - @VisibleForTesting public ConfiguredProfileBadgeConverter( final Clock clock, final BadgesConfiguration badgesConfiguration, diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/badges/LevelTranslator.java b/service/src/main/java/org/whispersystems/textsecuregcm/badges/LevelTranslator.java new file mode 100644 index 000000000..a9529b88d --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/badges/LevelTranslator.java @@ -0,0 +1,13 @@ +/* + * Copyright 2021 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.badges; + +import java.util.List; +import java.util.Locale; + +public interface LevelTranslator { + String translate(List acceptableLanguages, String badgeId); +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/badges/ResourceBundleLevelTranslator.java b/service/src/main/java/org/whispersystems/textsecuregcm/badges/ResourceBundleLevelTranslator.java new file mode 100644 index 000000000..807a1d537 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/badges/ResourceBundleLevelTranslator.java @@ -0,0 +1,32 @@ +/* + * Copyright 2021 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.badges; + +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.ResourceBundle; +import javax.annotation.Nonnull; +import org.signal.i18n.HeaderControlledResourceBundleLookup; + +public class ResourceBundleLevelTranslator implements LevelTranslator { + + private static final String BASE_NAME = "org.signal.subscriptions.Subscriptions"; + + private final HeaderControlledResourceBundleLookup headerControlledResourceBundleLookup; + + public ResourceBundleLevelTranslator( + @Nonnull final HeaderControlledResourceBundleLookup headerControlledResourceBundleLookup) { + this.headerControlledResourceBundleLookup = Objects.requireNonNull(headerControlledResourceBundleLookup); + } + + @Override + public String translate(final List acceptableLanguages, final String badgeId) { + final ResourceBundle resourceBundle = headerControlledResourceBundleLookup.getResourceBundle(BASE_NAME, + acceptableLanguages); + return resourceBundle.getString(badgeId); + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java index 062ab541e..3b9c50eda 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java @@ -64,6 +64,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.badges.BadgeTranslator; +import org.whispersystems.textsecuregcm.badges.LevelTranslator; import org.whispersystems.textsecuregcm.configuration.BoostConfiguration; import org.whispersystems.textsecuregcm.configuration.SubscriptionConfiguration; import org.whispersystems.textsecuregcm.configuration.SubscriptionLevelConfiguration; @@ -88,6 +89,7 @@ public class SubscriptionController { private final ServerZkReceiptOperations zkReceiptOperations; private final IssuedReceiptsManager issuedReceiptsManager; private final BadgeTranslator badgeTranslator; + private final LevelTranslator levelTranslator; public SubscriptionController( @Nonnull Clock clock, @@ -97,7 +99,8 @@ public class SubscriptionController { @Nonnull StripeManager stripeManager, @Nonnull ServerZkReceiptOperations zkReceiptOperations, @Nonnull IssuedReceiptsManager issuedReceiptsManager, - @Nonnull BadgeTranslator badgeTranslator) { + @Nonnull BadgeTranslator badgeTranslator, + @Nonnull LevelTranslator levelTranslator) { this.clock = Objects.requireNonNull(clock); this.subscriptionConfiguration = Objects.requireNonNull(subscriptionConfiguration); this.boostConfiguration = Objects.requireNonNull(boostConfiguration); @@ -106,6 +109,7 @@ public class SubscriptionController { this.zkReceiptOperations = Objects.requireNonNull(zkReceiptOperations); this.issuedReceiptsManager = Objects.requireNonNull(issuedReceiptsManager); this.badgeTranslator = Objects.requireNonNull(badgeTranslator); + this.levelTranslator = Objects.requireNonNull(levelTranslator); } @Timed @@ -347,17 +351,24 @@ public class SubscriptionController { public static class Level { + private final String name; private final Badge badge; private final Map currencies; @JsonCreator public Level( + @JsonProperty("name") String name, @JsonProperty("badge") Badge badge, @JsonProperty("currencies") Map currencies) { + this.name = name; this.badge = badge; this.currencies = currencies; } + public String getName() { + return name; + } + public Badge getBadge() { return badge; } @@ -391,6 +402,7 @@ public class SubscriptionController { GetLevelsResponse getLevelsResponse = new GetLevelsResponse( subscriptionConfiguration.getLevels().entrySet().stream().collect(Collectors.toMap(Entry::getKey, entry -> new GetLevelsResponse.Level( + levelTranslator.translate(acceptableLanguages, entry.getValue().getBadge()), badgeTranslator.translate(acceptableLanguages, entry.getValue().getBadge()), entry.getValue().getPrices().entrySet().stream().collect( Collectors.toMap(levelEntry -> levelEntry.getKey().toUpperCase(Locale.ROOT), diff --git a/service/src/main/resources/org/signal/subscriptions/Subscriptions.properties b/service/src/main/resources/org/signal/subscriptions/Subscriptions.properties index a1dc34f7f..97003986d 100644 --- a/service/src/main/resources/org/signal/subscriptions/Subscriptions.properties +++ b/service/src/main/resources/org/signal/subscriptions/Subscriptions.properties @@ -4,10 +4,10 @@ # # First subscription level -SUSTAINER1 = Sustainer 1 +R_LOW = Sustainer 1 # Second subscription level -SUSTAINER2 = Sustainer 2 +R_MED = Sustainer 2 # Third subscription level -SUSTAINER3 = Sustainer 3 +R_HIGH = Sustainer 3 diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java index 05fada921..964b57349 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java @@ -29,6 +29,7 @@ import org.signal.zkgroup.receipts.ServerZkReceiptOperations; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; import org.whispersystems.textsecuregcm.badges.BadgeTranslator; +import org.whispersystems.textsecuregcm.badges.LevelTranslator; import org.whispersystems.textsecuregcm.configuration.BoostConfiguration; import org.whispersystems.textsecuregcm.configuration.SubscriptionConfiguration; import org.whispersystems.textsecuregcm.configuration.SubscriptionLevelConfiguration; @@ -53,9 +54,10 @@ class SubscriptionControllerTest { private static final ServerZkReceiptOperations ZK_OPS = mock(ServerZkReceiptOperations.class); private static final IssuedReceiptsManager ISSUED_RECEIPTS_MANAGER = mock(IssuedReceiptsManager.class); private static final BadgeTranslator BADGE_TRANSLATOR = mock(BadgeTranslator.class); + private static final LevelTranslator LEVEL_TRANSLATOR = mock(LevelTranslator.class); private static final SubscriptionController SUBSCRIPTION_CONTROLLER = new SubscriptionController( CLOCK, SUBSCRIPTION_CONFIG, BOOST_CONFIG, SUBSCRIPTION_MANAGER, STRIPE_MANAGER, ZK_OPS, - ISSUED_RECEIPTS_MANAGER, BADGE_TRANSLATOR); + ISSUED_RECEIPTS_MANAGER, BADGE_TRANSLATOR, LEVEL_TRANSLATOR); private static final ResourceExtension RESOURCE_EXTENSION = ResourceExtension.builder() .addProperty(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE) .addProvider(AuthHelper.getAuthFilter()) @@ -69,7 +71,7 @@ class SubscriptionControllerTest { @AfterEach void tearDown() { reset(CLOCK, SUBSCRIPTION_CONFIG, SUBSCRIPTION_MANAGER, STRIPE_MANAGER, ZK_OPS, ISSUED_RECEIPTS_MANAGER, - BADGE_TRANSLATOR); + BADGE_TRANSLATOR, LEVEL_TRANSLATOR); } @Test @@ -85,6 +87,9 @@ class SubscriptionControllerTest { 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")))); when(BADGE_TRANSLATOR.translate(any(), eq("B3"))).thenReturn(new Badge("B3", "cat3", "name3", "desc3", 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")))); + when(LEVEL_TRANSLATOR.translate(any(), eq("B1"))).thenReturn("Z1"); + when(LEVEL_TRANSLATOR.translate(any(), eq("B2"))).thenReturn("Z2"); + when(LEVEL_TRANSLATOR.translate(any(), eq("B3"))).thenReturn("Z3"); GetLevelsResponse response = RESOURCE_EXTENSION.target("/v1/subscription/levels") .request() @@ -92,18 +97,21 @@ class SubscriptionControllerTest { assertThat(response.getLevels()).containsKeys(1L, 2L, 3L).satisfies(longLevelMap -> { assertThat(longLevelMap).extractingByKey(1L).satisfies(level -> { + assertThat(level.getName()).isEqualTo("Z1"); assertThat(level.getBadge().getId()).isEqualTo("B1"); assertThat(level.getCurrencies()).containsKeys("USD").extractingByKey("USD").satisfies(price -> { assertThat(price).isEqualTo("100"); }); }); assertThat(longLevelMap).extractingByKey(2L).satisfies(level -> { + assertThat(level.getName()).isEqualTo("Z2"); assertThat(level.getBadge().getId()).isEqualTo("B2"); assertThat(level.getCurrencies()).containsKeys("USD").extractingByKey("USD").satisfies(price -> { assertThat(price).isEqualTo("200"); }); }); assertThat(longLevelMap).extractingByKey(3L).satisfies(level -> { + assertThat(level.getName()).isEqualTo("Z3"); assertThat(level.getBadge().getId()).isEqualTo("B3"); assertThat(level.getCurrencies()).containsKeys("USD").extractingByKey("USD").satisfies(price -> { assertThat(price).isEqualTo("300");