From 7dbe26a5cdccf99fb543135cadb0c1f06705202c Mon Sep 17 00:00:00 2001 From: extio-de Date: Wed, 31 Dec 2025 13:30:35 +0100 Subject: [PATCH] feat(i18n): integrate StorageService for language persistence in LocalizationServiceImpl --- .../i18n/LocalizationAutoConfiguration.java | 5 +- .../game_engine/i18n/LocalizationEditor.java | 2 +- .../i18n/LocalizationServiceImpl.java | 40 +++++++++++++++ .../renderer/g2d/theme/G2DThemeManager.java | 2 +- .../i18n/LocalizationManagerTest.java | 49 ++++++++++++++++++- .../g2d/theme/G2DThemeManagerTest.java | 33 ++++++++----- 6 files changed, 113 insertions(+), 18 deletions(-) diff --git a/src/main/java/de/extio/game_engine/i18n/LocalizationAutoConfiguration.java b/src/main/java/de/extio/game_engine/i18n/LocalizationAutoConfiguration.java index fa03c8c..231927e 100644 --- a/src/main/java/de/extio/game_engine/i18n/LocalizationAutoConfiguration.java +++ b/src/main/java/de/extio/game_engine/i18n/LocalizationAutoConfiguration.java @@ -15,6 +15,7 @@ import org.springframework.context.annotation.Bean; import de.extio.game_engine.resource.StaticResource; import de.extio.game_engine.resource.StaticResourceService; +import de.extio.game_engine.storage.StorageService; @AutoConfiguration @ConditionalOnProperty(name = "game-engine.i18n.enabled", havingValue = "true", matchIfMissing = true) @@ -24,8 +25,8 @@ public class LocalizationAutoConfiguration { @Bean @ConditionalOnMissingBean - LocalizationService localizationManager(@Value("${game-engine.i18n.load-on-start:true}") final boolean loadOnStart, @Value("${game-engine.i18n.resource:i18n/i18n.yaml}") final String resource, final StaticResourceService staticResourceService) { - final var localizationManager = new LocalizationServiceImpl(); + LocalizationService localizationManager(@Value("${game-engine.i18n.load-on-start:true}") final boolean loadOnStart, @Value("${game-engine.i18n.resource:i18n/i18n.yaml}") final String resource, final StaticResourceService staticResourceService, final StorageService storageService) { + final var localizationManager = new LocalizationServiceImpl(storageService); if (loadOnStart) { final var path = new File(resource).toPath(); diff --git a/src/main/java/de/extio/game_engine/i18n/LocalizationEditor.java b/src/main/java/de/extio/game_engine/i18n/LocalizationEditor.java index bc32e94..f6f189f 100644 --- a/src/main/java/de/extio/game_engine/i18n/LocalizationEditor.java +++ b/src/main/java/de/extio/game_engine/i18n/LocalizationEditor.java @@ -137,7 +137,7 @@ public class LocalizationEditor extends JFrame { * Create the frame. */ public LocalizationEditor() { - this.localizationManagerImpl = new LocalizationServiceImpl(); + this.localizationManagerImpl = new LocalizationServiceImpl(null); this.setTitle("i18n Editor"); this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); diff --git a/src/main/java/de/extio/game_engine/i18n/LocalizationServiceImpl.java b/src/main/java/de/extio/game_engine/i18n/LocalizationServiceImpl.java index b5c43be..735a5e3 100644 --- a/src/main/java/de/extio/game_engine/i18n/LocalizationServiceImpl.java +++ b/src/main/java/de/extio/game_engine/i18n/LocalizationServiceImpl.java @@ -5,10 +5,12 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import de.extio.game_engine.storage.StorageService; import de.extio.game_engine.util.ObjectSerialization; public class LocalizationServiceImpl implements LocalizationService { @@ -17,12 +19,21 @@ public class LocalizationServiceImpl implements LocalizationService { public final static String NOT_FOUND_PREFIX = "{i18n."; + private static final List LANGUAGE_STORAGE_PATH = List.of("gameEngine"); + private static final String LANGUAGE_STORAGE_NAME = "currentLanguage"; + + private final StorageService storageService; + private Localizations localizations = new Localizations(); private Map currentLanguage; private String currentLanguageName; + LocalizationServiceImpl(final StorageService storageService) { + this.storageService = storageService; + } + @Override public void resetEntries() { this.localizations.getLanguages().forEach((lang, mapping) -> mapping.clear()); @@ -74,10 +85,12 @@ public class LocalizationServiceImpl implements LocalizationService { LOGGER.info("Loaded {} localizations for {} languages", localizations.getDescriptions().size(), localizations.getLanguages().size()); } + this.loadPersistedLanguageName(); if (this.currentLanguageName == null) { this.currentLanguageName = "en"; } this.setLanguage(this.currentLanguageName); + } @Override @@ -88,6 +101,7 @@ public class LocalizationServiceImpl implements LocalizationService { this.currentLanguageName = lang; this.currentLanguage = this.localizations.getLanguages().get(lang); + this.persistCurrentLanguageName(); } @Override @@ -169,4 +183,30 @@ public class LocalizationServiceImpl implements LocalizationService { } } + private void persistCurrentLanguageName() { + if (this.storageService == null) { + return; + } + try { + this.storageService.store(LANGUAGE_STORAGE_PATH, LANGUAGE_STORAGE_NAME, this.currentLanguageName); + } + catch (final Exception e) { + LOGGER.warn("Could not persist current language name", e); + } + } + + private void loadPersistedLanguageName() { + if (this.storageService == null) { + return; + } + try { + final Optional loaded = this.storageService.loadByPath(String.class, LANGUAGE_STORAGE_PATH, LANGUAGE_STORAGE_NAME); + if (loaded.isPresent()) { + this.currentLanguageName = loaded.get(); + } + } + catch (final Exception e) { + LOGGER.warn("Could not load persisted language name", e); + } + } } diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManager.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManager.java index 94401f3..2cd52a8 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManager.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManager.java @@ -22,7 +22,7 @@ public class G2DThemeManager implements ThemeManager { private static final Logger LOGGER = LoggerFactory.getLogger(G2DThemeManager.class); - private static final List THEME_STORAGE_PATH = List.of("gameEngine", "themes"); + private static final List THEME_STORAGE_PATH = List.of("gameEngine"); private static final String THEME_STORAGE_NAME = "lastTheme"; private G2DRenderer g2dRenderer; diff --git a/src/test/java/de/extio/game_engine/i18n/LocalizationManagerTest.java b/src/test/java/de/extio/game_engine/i18n/LocalizationManagerTest.java index a03a897..f032fd7 100644 --- a/src/test/java/de/extio/game_engine/i18n/LocalizationManagerTest.java +++ b/src/test/java/de/extio/game_engine/i18n/LocalizationManagerTest.java @@ -10,17 +10,29 @@ import java.io.InputStream; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import de.extio.game_engine.storage.StorageService; class LocalizationManagerTest { + @Mock + private StorageService storageService; + private LocalizationService localizationManager; @BeforeEach void setUp() { - this.localizationManager = new LocalizationServiceImpl(); + MockitoAnnotations.openMocks(this); + this.localizationManager = new LocalizationServiceImpl(storageService); } @Test @@ -278,4 +290,39 @@ class LocalizationManagerTest { assertEquals("Hello", englishGreeting); assertEquals("Hallo", germanGreeting); } + + @Test + void testPersistCurrentLanguage() { + Localizations localizations = this.localizationManager.getLocalizations(); + Language english = new Language("English", "en"); + localizations.getLanguagesInfo().put("en", english); + localizations.getLanguages().put("en", new LinkedHashMap<>(Map.of("greeting", "Hello"))); + + this.localizationManager.setLanguage("en"); + + verify(storageService).store(List.of("gameEngine"), "currentLanguage", "en"); + } + + @Test + void testLoadPersistedLanguage() { + when(storageService.loadByPath(String.class, List.of("gameEngine"), "currentLanguage")) + .thenReturn(Optional.of("de")); + + // Load localizations with "de" + Localizations localizationsToSave = new Localizations(); + Language german = new Language("German", "de"); + Map germanEntries = new LinkedHashMap<>(Map.of("greeting", "Hallo")); + localizationsToSave.getLanguagesInfo().put("de", german); + localizationsToSave.getLanguages().put("de", germanEntries); + localizationsToSave.setPrefix("i18n."); + localizationsToSave.setCurId(1); + + byte[] serialized = de.extio.game_engine.util.ObjectSerialization.serialize( + localizationsToSave, false, false, false, null, digest -> {}); + InputStream stream = new ByteArrayInputStream(serialized); + + this.localizationManager.load(stream); + + assertEquals("de", this.localizationManager.getCurrentLanguage()); + } } diff --git a/src/test/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManagerTest.java b/src/test/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManagerTest.java index e597cb8..166d7fc 100644 --- a/src/test/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManagerTest.java +++ b/src/test/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManagerTest.java @@ -16,6 +16,9 @@ import java.util.Optional; import org.junit.jupiter.api.Test; +import de.extio.game_engine.renderer.g2d.G2DRenderer; +import de.extio.game_engine.renderer.g2d.G2DRendererControl; +import de.extio.game_engine.renderer.model.options.UiOptions; import de.extio.game_engine.resource.StaticResource; import de.extio.game_engine.resource.StaticResourceService; import de.extio.game_engine.storage.StorageService; @@ -42,7 +45,7 @@ public class G2DThemeManagerTest { } @Test - void loadsLastUsedThemeFromStorageOnConstruction() { + void loadsLastUsedThemeFromStorage() { final StaticResourceService staticResourceService = org.mockito.Mockito.mock(StaticResourceService.class); final StorageService storageService = org.mockito.Mockito.mock(StorageService.class); final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.class); @@ -50,7 +53,7 @@ public class G2DThemeManagerTest { final Theme defaultTheme = theme("Default"); final Theme persistedTheme = theme("Persisted"); - when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine", "themes")), eq("lastTheme"))) + when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine")), eq("lastTheme"))) .thenReturn(Optional.of(persistedTheme)); final var manager = new G2DThemeManager( @@ -59,19 +62,21 @@ public class G2DThemeManagerTest { List.of(patternRenderer), Map.of("defaultKey", defaultTheme), "defaultKey"); + manager.setCurrentTheme((Theme) null); assertEquals("Persisted", manager.getCurrentTheme().getName()); - assertTrue(manager.getAvailableThemeNames().contains("Persisted")); } @Test void setCurrentThemePersistsLastThemeObject() { final StaticResourceService staticResourceService = org.mockito.Mockito.mock(StaticResourceService.class); final StorageService storageService = org.mockito.Mockito.mock(StorageService.class); - final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.class); - - when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine", "themes")), eq("lastTheme"))) + when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine")), eq("lastTheme"))) .thenReturn(Optional.empty()); + final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.class); + final G2DRendererControl g2dRendererControl = org.mockito.Mockito.mock(G2DRendererControl.class); + when(g2dRendererControl.getUiOptions()).thenReturn(new UiOptions()); + final G2DRenderer g2dRenderer = org.mockito.Mockito.mock(G2DRenderer.class); final Theme defaultTheme = theme("Default"); final var manager = new G2DThemeManager( @@ -80,11 +85,13 @@ public class G2DThemeManagerTest { List.of(patternRenderer), Map.of("defaultKey", defaultTheme), "defaultKey"); + manager.setG2dRendererControl(g2dRendererControl); + manager.setG2dRenderer(g2dRenderer); final Theme selected = theme("Selected"); manager.setCurrentTheme(selected); - verify(storageService).store(eq(List.of("gameEngine", "themes")), eq("lastTheme"), eq(selected)); + verify(storageService).store(eq(List.of("gameEngine")), eq("lastTheme"), eq(selected)); assertTrue(manager.getAvailableThemeNames().contains("Selected")); } @@ -94,7 +101,7 @@ public class G2DThemeManagerTest { final StorageService storageService = org.mockito.Mockito.mock(StorageService.class); final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.class); - when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine", "themes")), eq("lastTheme"))) + when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine")), eq("lastTheme"))) .thenReturn(Optional.empty()); final Theme defaultTheme = theme("Default"); @@ -106,7 +113,7 @@ public class G2DThemeManagerTest { "defaultKey"); final Theme loaded = theme("FromStorage"); - when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine", "themes")), eq("FromStorage"))) + when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine")), eq("FromStorage"))) .thenReturn(Optional.of(loaded)); final Optional result = manager.loadThemeFromStorage("FromStorage"); @@ -121,7 +128,7 @@ public class G2DThemeManagerTest { final StorageService storageService = org.mockito.Mockito.mock(StorageService.class); final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.class); - when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine", "themes")), eq("lastTheme"))) + when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine")), eq("lastTheme"))) .thenReturn(Optional.empty()); final Theme defaultTheme = theme("Default"); @@ -149,7 +156,7 @@ public class G2DThemeManagerTest { final StorageService storageService = org.mockito.Mockito.mock(StorageService.class); final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.class); - when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine", "themes")), eq("lastTheme"))) + when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine")), eq("lastTheme"))) .thenReturn(Optional.empty()); final Theme defaultTheme = theme("Default"); @@ -163,7 +170,7 @@ public class G2DThemeManagerTest { final Theme themeToSave = theme("UserTheme"); manager.saveThemeToStorage(themeToSave); - verify(storageService).store(eq(List.of("gameEngine", "themes")), eq("UserTheme"), eq(themeToSave)); + verify(storageService).store(eq(List.of("gameEngine")), eq("UserTheme"), eq(themeToSave)); } @Test @@ -204,7 +211,7 @@ public class G2DThemeManagerTest { final StorageService storageService = org.mockito.Mockito.mock(StorageService.class); final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.class); - when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine", "themes")), eq("lastTheme"))) + when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine")), eq("lastTheme"))) .thenReturn(Optional.empty()); final Theme defaultTheme = theme("Default");