From 2af25f730119ce0a37f74aa8567d865d901fb8a4 Mon Sep 17 00:00:00 2001 From: Yun Wang Date: Wed, 2 Sep 2026 11:36:25 +0200 Subject: [PATCH 1/2] feat: support multiple auto-translation languages The backend accepts up to five comma-separated values in auto_translation_language, but ChannelRequestObject only exposed a single Language enum, so calling the builder setter per language kept only the last one. Add autoTranslationLanguages(List), deprecate the single language setter, and serialize either into the comma-separated list. --- docs/features/translation.md | 14 +++- .../getstream/chat/java/models/Channel.java | 28 +++++++- .../getstream/chat/java/models/Language.java | 14 +++- .../ChannelAutoTranslationLanguageTest.java | 71 +++++++++++++++++++ 4 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 src/test/java/io/getstream/chat/java/ChannelAutoTranslationLanguageTest.java 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..a0c4af462 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,13 @@ 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.Objects; +import java.util.stream.Collectors; import lombok.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -272,9 +275,30 @@ 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; + } + return languages.stream() + .filter(Objects::nonNull) + .map(Language::getValue) + .collect(Collectors.joining(",")); + } @Nullable @JsonProperty("frozen") @@ -321,6 +345,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 +356,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..b91fd1fe9 --- /dev/null +++ b/src/test/java/io/getstream/chat/java/ChannelAutoTranslationLanguageTest.java @@ -0,0 +1,71 @@ +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("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")); + } +} From ff157a935350588576bab726be0da0ecaaef4326 Mon Sep 17 00:00:00 2001 From: Yun Wang Date: Wed, 2 Sep 2026 12:20:21 +0200 Subject: [PATCH 2/2] fix: treat an empty auto-translation language list as unset Collectors.joining returned an empty string for an empty list, or for a list holding only nulls, putting auto_translation_language="" on the wire. Return null instead, and drop Language.UNKNOWN, which the deserializer hands back for any code this SDK does not know. --- .../getstream/chat/java/models/Channel.java | 11 ++++---- .../ChannelAutoTranslationLanguageTest.java | 27 +++++++++++++++++++ .../io/getstream/chat/java/LanguageTest.java | 25 +++++++++++++++++ 3 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 src/test/java/io/getstream/chat/java/LanguageTest.java 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 a0c4af462..cf2873226 100644 --- a/src/main/java/io/getstream/chat/java/models/Channel.java +++ b/src/main/java/io/getstream/chat/java/models/Channel.java @@ -35,7 +35,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.stream.Collectors; import lombok.*; import org.jetbrains.annotations.NotNull; @@ -294,10 +293,12 @@ public String getAutoTranslationLanguageValue() { if (languages == null) { return null; } - return languages.stream() - .filter(Objects::nonNull) - .map(Language::getValue) - .collect(Collectors.joining(",")); + String value = + languages.stream() + .filter(language -> language != null && language != Language.UNKNOWN) + .map(Language::getValue) + .collect(Collectors.joining(",")); + return value.isEmpty() ? null : value; } @Nullable diff --git a/src/test/java/io/getstream/chat/java/ChannelAutoTranslationLanguageTest.java b/src/test/java/io/getstream/chat/java/ChannelAutoTranslationLanguageTest.java index b91fd1fe9..95591347e 100644 --- a/src/test/java/io/getstream/chat/java/ChannelAutoTranslationLanguageTest.java +++ b/src/test/java/io/getstream/chat/java/ChannelAutoTranslationLanguageTest.java @@ -59,6 +59,33 @@ void whenSettingBoth_thenListWins() { 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 { 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()); + } + } +}