diff --git a/docs/features/translation.md b/docs/features/translation.md index 6a1c6eeea..815d19574 100644 --- a/docs/features/translation.md +++ b/docs/features/translation.md @@ -104,7 +104,16 @@ Channel.update("channeltype", "channelid") .data(ChannelRequestObject .builder() .autoTranslationEnabled(true) - .autoTranslationLanguage(Language.EN) + .autoTranslationLanguages(List.of(Language.EN)) + .build()) + .request(); + +// translate messages into up to five languages +Channel.update("channeltype", "channelid") + .data(ChannelRequestObject + .builder() + .autoTranslationEnabled(true) + .autoTranslationLanguages(List.of(Language.EN, Language.FR, Language.IT)) .build()) .request(); @@ -112,6 +121,9 @@ Channel.update("channeltype", "channelid") App.update().autoTranslationEnabled(true).request(); ``` +> [!NOTE] +> `autoTranslationLanguages` accepts up to five languages and can only be set on channel create or full update. Partial update rejects `auto_translation_language` as a reserved field. The single language `autoTranslationLanguage(Language)` setter is deprecated: calling it more than once keeps only the last language. + ### Set user language In order for auto translation to work, you must set the user language or specify a destination language for the channel using the `auto_translation_language` field (see previous code example). diff --git a/src/main/java/io/getstream/chat/java/models/Channel.java b/src/main/java/io/getstream/chat/java/models/Channel.java index aec20893a..cf2873226 100644 --- a/src/main/java/io/getstream/chat/java/models/Channel.java +++ b/src/main/java/io/getstream/chat/java/models/Channel.java @@ -30,10 +30,12 @@ import io.getstream.chat.java.models.framework.StreamResponseObject; import io.getstream.chat.java.services.ChannelService; import io.getstream.chat.java.services.framework.Client; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import lombok.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -272,9 +274,32 @@ public static class ChannelRequestObject { @JsonProperty("auto_translation_enabled") private Boolean autoTranslationEnabled; + /** + * @deprecated the backend accepts up to five languages, use {@code + * autoTranslationLanguages(List)} instead. + */ + @Nullable @Deprecated @JsonIgnore private Language autoTranslationLanguage; + + /** Up to five languages. Can only be set on channel create or full update. */ + @Nullable @JsonIgnore private List autoTranslationLanguages; + @Nullable @JsonProperty("auto_translation_language") - private Language autoTranslationLanguage; + public String getAutoTranslationLanguageValue() { + List languages = autoTranslationLanguages; + if (languages == null && autoTranslationLanguage != null) { + languages = Collections.singletonList(autoTranslationLanguage); + } + if (languages == null) { + return null; + } + String value = + languages.stream() + .filter(language -> language != null && language != Language.UNKNOWN) + .map(Language::getValue) + .collect(Collectors.joining(",")); + return value.isEmpty() ? null : value; + } @Nullable @JsonProperty("frozen") @@ -321,6 +346,7 @@ private ChannelRequestObject( @Nullable String team, @Nullable Boolean autoTranslationEnabled, @Nullable Language autoTranslationLanguage, + @Nullable List autoTranslationLanguages, @Nullable Boolean frozen, @Nullable List members, @Nullable List invites, @@ -331,6 +357,7 @@ private ChannelRequestObject( this.team = team; this.autoTranslationEnabled = autoTranslationEnabled; this.autoTranslationLanguage = autoTranslationLanguage; + this.autoTranslationLanguages = autoTranslationLanguages; this.frozen = frozen; this.members = members; this.invites = invites; diff --git a/src/main/java/io/getstream/chat/java/models/Language.java b/src/main/java/io/getstream/chat/java/models/Language.java index 59a662213..c15044804 100644 --- a/src/main/java/io/getstream/chat/java/models/Language.java +++ b/src/main/java/io/getstream/chat/java/models/Language.java @@ -117,5 +117,17 @@ public enum Language { @JsonProperty("zh-TW") ZH_TW, @JsonEnumDefaultValue - UNKNOWN + UNKNOWN; + + /** + * @return the value sent over the wire, e.g. {@code "es-MX"} for {@link Language#ES_MX} + */ + public String getValue() { + try { + JsonProperty jsonProperty = Language.class.getField(name()).getAnnotation(JsonProperty.class); + return jsonProperty == null ? name() : jsonProperty.value(); + } catch (NoSuchFieldException e) { + throw new IllegalStateException("Unknown language constant " + name(), e); + } + } } diff --git a/src/test/java/io/getstream/chat/java/ChannelAutoTranslationLanguageTest.java b/src/test/java/io/getstream/chat/java/ChannelAutoTranslationLanguageTest.java new file mode 100644 index 000000000..95591347e --- /dev/null +++ b/src/test/java/io/getstream/chat/java/ChannelAutoTranslationLanguageTest.java @@ -0,0 +1,98 @@ +package io.getstream.chat.java; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.getstream.chat.java.models.Channel.ChannelRequestObject; +import io.getstream.chat.java.models.Language; +import java.util.Arrays; +import java.util.Collections; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class ChannelAutoTranslationLanguageTest { + + // Mirrors the visibility configuration of DefaultClient's mapper. + private static final ObjectMapper MAPPER = + new ObjectMapper() + .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) + .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + + @DisplayName("Multiple languages serialize as a comma separated list") + @Test + void whenSettingLanguages_thenSerializedAsCommaSeparatedList() throws Exception { + ChannelRequestObject channel = + ChannelRequestObject.builder() + .autoTranslationEnabled(true) + .autoTranslationLanguages(Arrays.asList(Language.IT, Language.FR, Language.ES_MX)) + .build(); + + Assertions.assertEquals("it,fr,es-MX", channel.getAutoTranslationLanguageValue()); + Assertions.assertTrue( + MAPPER + .writeValueAsString(channel) + .contains("\"auto_translation_language\":\"it,fr,es-MX\"")); + } + + @DisplayName("Single language setter keeps working") + @Test + void whenSettingSingleLanguage_thenSerializedAsSingleValue() throws Exception { + @SuppressWarnings("deprecation") + ChannelRequestObject channel = + ChannelRequestObject.builder().autoTranslationLanguage(Language.IT).build(); + + Assertions.assertTrue( + MAPPER.writeValueAsString(channel).contains("\"auto_translation_language\":\"it\"")); + } + + @DisplayName("The language list wins over the single language") + @Test + void whenSettingBoth_thenListWins() { + @SuppressWarnings("deprecation") + ChannelRequestObject channel = + ChannelRequestObject.builder() + .autoTranslationLanguage(Language.IT) + .autoTranslationLanguages(Collections.singletonList(Language.FR)) + .build(); + + Assertions.assertEquals("fr", channel.getAutoTranslationLanguageValue()); + } + + @DisplayName("An empty list serializes as null") + @Test + void whenSettingEmptyList_thenSerializedAsNull() throws Exception { + ChannelRequestObject channel = + ChannelRequestObject.builder().autoTranslationLanguages(Collections.emptyList()).build(); + + Assertions.assertNull(channel.getAutoTranslationLanguageValue()); + Assertions.assertTrue( + MAPPER.writeValueAsString(channel).contains("\"auto_translation_language\":null")); + } + + @DisplayName("Null and unknown languages are dropped") + @Test + void whenListHoldsNullOrUnknown_thenTheyAreDropped() { + ChannelRequestObject channel = + ChannelRequestObject.builder() + .autoTranslationLanguages(Arrays.asList(Language.EN, null, Language.UNKNOWN)) + .build(); + + Assertions.assertEquals("en", channel.getAutoTranslationLanguageValue()); + Assertions.assertNull( + ChannelRequestObject.builder() + .autoTranslationLanguages(Arrays.asList(null, Language.UNKNOWN)) + .build() + .getAutoTranslationLanguageValue()); + } + + @DisplayName("No language set serializes as null") + @Test + void whenSettingNoLanguage_thenSerializedAsNull() throws Exception { + ChannelRequestObject channel = ChannelRequestObject.builder().build(); + + Assertions.assertNull(channel.getAutoTranslationLanguageValue()); + Assertions.assertTrue( + MAPPER.writeValueAsString(channel).contains("\"auto_translation_language\":null")); + } +} diff --git a/src/test/java/io/getstream/chat/java/LanguageTest.java b/src/test/java/io/getstream/chat/java/LanguageTest.java new file mode 100644 index 000000000..fa59d74ff --- /dev/null +++ b/src/test/java/io/getstream/chat/java/LanguageTest.java @@ -0,0 +1,25 @@ +package io.getstream.chat.java; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.getstream.chat.java.models.Language; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class LanguageTest { + + @DisplayName("Every language returns its declared wire value") + @Test + void whenReadingValue_thenItMatchesTheDeclaredJsonProperty() throws Exception { + for (Language language : Language.values()) { + JsonProperty jsonProperty = + Language.class.getField(language.name()).getAnnotation(JsonProperty.class); + if (language == Language.UNKNOWN) { + Assertions.assertNull(jsonProperty); + continue; + } + Assertions.assertNotNull(jsonProperty, language.name() + " is missing @JsonProperty"); + Assertions.assertEquals(jsonProperty.value(), language.getValue()); + } + } +}