Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions src/main/java/com/faforever/client/remote/FafServerAccessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@
import reactor.util.retry.Retry;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -278,7 +280,8 @@ private void onNotice(NoticeInfo noticeMessage) {
taskScheduler.scheduleWithFixedDelay(Platform::exit, Duration.ofSeconds(10));
}

if (noticeMessage.getText() == null) {
String localizedText = getLocalizedNoticeText(noticeMessage, noticeMessage.getText());
if (localizedText == null) {
return;
}

Expand All @@ -294,10 +297,33 @@ private void onNotice(NoticeInfo noticeMessage) {
};
}
notificationService.addNotification(
new ServerNotification(i18n.get("messageFromServer"), noticeMessage.getText(), severity,
new ServerNotification(i18n.get("messageFromServer"), localizedText, severity,
Collections.singletonList(new DismissAction(i18n))));
}

String getLocalizedNoticeText(Object noticeMessage, String fallbackText) {
Optional<String> i18nKey = getOptionalNoticeValue(noticeMessage, "getI18nKey", String.class);
if (i18nKey.isEmpty()) {
return fallbackText;
}

Object[] args = getOptionalNoticeValue(noticeMessage, "getI18nArgs", Object.class)
.map(value -> value instanceof Collection<?> collection ? collection.toArray() : new Object[] {value})
.orElseGet(() -> new Object[] {});

return i18n.getOrDefault(fallbackText, i18nKey.get(), args);
}

private <T> Optional<T> getOptionalNoticeValue(Object noticeMessage, String methodName, Class<T> type) {
try {
Method method = noticeMessage.getClass().getMethod(methodName);
Object value = method.invoke(noticeMessage);
return type.isInstance(value) ? Optional.of(type.cast(value)) : Optional.empty();
} catch (ReflectiveOperationException e) {
return Optional.empty();
}
}

public void restoreGameSession(int id) {
lobbyClient.restoreGameSession(id);
}
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/i18n/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ dismiss = Dismiss
errorTitle = Uh oh\!
bytesProgress = {0} of {1}
messageFromServer = Message from server
login.error.banned = You are banned from FAF until {0}. <br>Reason: <br>{1}<br><br><i>If you would like to appeal this ban, please send an email to: moderation@faforever.com</i>
more = More
less=Less
searchResult = Search result
Expand Down
34 changes: 34 additions & 0 deletions src/test/java/com/faforever/client/remote/ServerAccessorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,30 @@ public void testOnNotice() throws Exception {
verify(i18n).get("messageFromServer");
}

@Test
public void testLocalizedNoticeTextUsesI18nMetadataWhenPresent() {
LocalizableNoticeInfo noticeMessage = new LocalizableNoticeInfo("login.error.banned", List.of("2026-06-01"));

when(i18n.getOrDefault("You are banned from FAF until 2026-06-01", "login.error.banned", "2026-06-01"))
.thenReturn("Localized ban notice");

String text = instance.getLocalizedNoticeText(noticeMessage, "You are banned from FAF until 2026-06-01");

assertThat(text, is("Localized ban notice"));
}

@Test
public void testLocalizedNoticeTextSupportsMetadataOnlyNotices() {
LocalizableNoticeInfo noticeMessage = new LocalizableNoticeInfo("login.error.banned", List.of("2026-06-01"));

when(i18n.getOrDefault((String) null, "login.error.banned", "2026-06-01"))
.thenReturn("Localized ban notice");

String text = instance.getLocalizedNoticeText(noticeMessage, null);

assertThat(text, is("Localized ban notice"));
}

@Test
public void onKickNoticeStopsApplication() throws Exception {
NoticeInfo noticeMessage = new NoticeInfo("kick", null);
Expand All @@ -342,6 +366,16 @@ public void onKickNoticeStopsApplication() throws Exception {
verify(taskScheduler, timeout(10000)).scheduleWithFixedDelay(any(Runnable.class), any(Duration.class));
}

private record LocalizableNoticeInfo(String i18nKey, List<Object> i18nArgs) {
public String getI18nKey() {
return i18nKey;
}

public List<Object> getI18nArgs() {
return i18nArgs;
}
}

@Test
public void testRequestHostGame() {
NewGameInfo newGameInfo = NewGameInfoBuilder.create()
Expand Down