feat(i18n): integrate StorageService for language persistence in LocalizationServiceImpl

This commit is contained in:
extio-de
2025-12-31 13:30:35 +01:00
parent 5a1807d331
commit 7dbe26a5cd
6 changed files with 113 additions and 18 deletions

View File

@@ -15,6 +15,7 @@ import org.springframework.context.annotation.Bean;
import de.extio.game_engine.resource.StaticResource; import de.extio.game_engine.resource.StaticResource;
import de.extio.game_engine.resource.StaticResourceService; import de.extio.game_engine.resource.StaticResourceService;
import de.extio.game_engine.storage.StorageService;
@AutoConfiguration @AutoConfiguration
@ConditionalOnProperty(name = "game-engine.i18n.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(name = "game-engine.i18n.enabled", havingValue = "true", matchIfMissing = true)
@@ -24,8 +25,8 @@ public class LocalizationAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @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) { 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(); final var localizationManager = new LocalizationServiceImpl(storageService);
if (loadOnStart) { if (loadOnStart) {
final var path = new File(resource).toPath(); final var path = new File(resource).toPath();

View File

@@ -137,7 +137,7 @@ public class LocalizationEditor extends JFrame {
* Create the frame. * Create the frame.
*/ */
public LocalizationEditor() { public LocalizationEditor() {
this.localizationManagerImpl = new LocalizationServiceImpl(); this.localizationManagerImpl = new LocalizationServiceImpl(null);
this.setTitle("i18n Editor"); this.setTitle("i18n Editor");
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

View File

@@ -5,10 +5,12 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Optional;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import de.extio.game_engine.storage.StorageService;
import de.extio.game_engine.util.ObjectSerialization; import de.extio.game_engine.util.ObjectSerialization;
public class LocalizationServiceImpl implements LocalizationService { public class LocalizationServiceImpl implements LocalizationService {
@@ -17,12 +19,21 @@ public class LocalizationServiceImpl implements LocalizationService {
public final static String NOT_FOUND_PREFIX = "{i18n."; public final static String NOT_FOUND_PREFIX = "{i18n.";
private static final List<String> LANGUAGE_STORAGE_PATH = List.of("gameEngine");
private static final String LANGUAGE_STORAGE_NAME = "currentLanguage";
private final StorageService storageService;
private Localizations localizations = new Localizations(); private Localizations localizations = new Localizations();
private Map<String, String> currentLanguage; private Map<String, String> currentLanguage;
private String currentLanguageName; private String currentLanguageName;
LocalizationServiceImpl(final StorageService storageService) {
this.storageService = storageService;
}
@Override @Override
public void resetEntries() { public void resetEntries() {
this.localizations.getLanguages().forEach((lang, mapping) -> mapping.clear()); 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()); LOGGER.info("Loaded {} localizations for {} languages", localizations.getDescriptions().size(), localizations.getLanguages().size());
} }
this.loadPersistedLanguageName();
if (this.currentLanguageName == null) { if (this.currentLanguageName == null) {
this.currentLanguageName = "en"; this.currentLanguageName = "en";
} }
this.setLanguage(this.currentLanguageName); this.setLanguage(this.currentLanguageName);
} }
@Override @Override
@@ -88,6 +101,7 @@ public class LocalizationServiceImpl implements LocalizationService {
this.currentLanguageName = lang; this.currentLanguageName = lang;
this.currentLanguage = this.localizations.getLanguages().get(lang); this.currentLanguage = this.localizations.getLanguages().get(lang);
this.persistCurrentLanguageName();
} }
@Override @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<String> 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);
}
}
} }

View File

@@ -22,7 +22,7 @@ public class G2DThemeManager implements ThemeManager {
private static final Logger LOGGER = LoggerFactory.getLogger(G2DThemeManager.class); private static final Logger LOGGER = LoggerFactory.getLogger(G2DThemeManager.class);
private static final List<String> THEME_STORAGE_PATH = List.of("gameEngine", "themes"); private static final List<String> THEME_STORAGE_PATH = List.of("gameEngine");
private static final String THEME_STORAGE_NAME = "lastTheme"; private static final String THEME_STORAGE_NAME = "lastTheme";
private G2DRenderer g2dRenderer; private G2DRenderer g2dRenderer;

View File

@@ -10,17 +10,29 @@ import java.io.InputStream;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; 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 { class LocalizationManagerTest {
@Mock
private StorageService storageService;
private LocalizationService localizationManager; private LocalizationService localizationManager;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
this.localizationManager = new LocalizationServiceImpl(); MockitoAnnotations.openMocks(this);
this.localizationManager = new LocalizationServiceImpl(storageService);
} }
@Test @Test
@@ -278,4 +290,39 @@ class LocalizationManagerTest {
assertEquals("Hello", englishGreeting); assertEquals("Hello", englishGreeting);
assertEquals("Hallo", germanGreeting); 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<String, String> 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());
}
} }

View File

@@ -16,6 +16,9 @@ import java.util.Optional;
import org.junit.jupiter.api.Test; 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.StaticResource;
import de.extio.game_engine.resource.StaticResourceService; import de.extio.game_engine.resource.StaticResourceService;
import de.extio.game_engine.storage.StorageService; import de.extio.game_engine.storage.StorageService;
@@ -42,7 +45,7 @@ public class G2DThemeManagerTest {
} }
@Test @Test
void loadsLastUsedThemeFromStorageOnConstruction() { void loadsLastUsedThemeFromStorage() {
final StaticResourceService staticResourceService = org.mockito.Mockito.mock(StaticResourceService.class); final StaticResourceService staticResourceService = org.mockito.Mockito.mock(StaticResourceService.class);
final StorageService storageService = org.mockito.Mockito.mock(StorageService.class); final StorageService storageService = org.mockito.Mockito.mock(StorageService.class);
final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.class); final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.class);
@@ -50,7 +53,7 @@ public class G2DThemeManagerTest {
final Theme defaultTheme = theme("Default"); final Theme defaultTheme = theme("Default");
final Theme persistedTheme = theme("Persisted"); 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)); .thenReturn(Optional.of(persistedTheme));
final var manager = new G2DThemeManager( final var manager = new G2DThemeManager(
@@ -59,19 +62,21 @@ public class G2DThemeManagerTest {
List.of(patternRenderer), List.of(patternRenderer),
Map.of("defaultKey", defaultTheme), Map.of("defaultKey", defaultTheme),
"defaultKey"); "defaultKey");
manager.setCurrentTheme((Theme) null);
assertEquals("Persisted", manager.getCurrentTheme().getName()); assertEquals("Persisted", manager.getCurrentTheme().getName());
assertTrue(manager.getAvailableThemeNames().contains("Persisted"));
} }
@Test @Test
void setCurrentThemePersistsLastThemeObject() { void setCurrentThemePersistsLastThemeObject() {
final StaticResourceService staticResourceService = org.mockito.Mockito.mock(StaticResourceService.class); final StaticResourceService staticResourceService = org.mockito.Mockito.mock(StaticResourceService.class);
final StorageService storageService = org.mockito.Mockito.mock(StorageService.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")), eq("lastTheme")))
when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine", "themes")), eq("lastTheme")))
.thenReturn(Optional.empty()); .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 Theme defaultTheme = theme("Default");
final var manager = new G2DThemeManager( final var manager = new G2DThemeManager(
@@ -80,11 +85,13 @@ public class G2DThemeManagerTest {
List.of(patternRenderer), List.of(patternRenderer),
Map.of("defaultKey", defaultTheme), Map.of("defaultKey", defaultTheme),
"defaultKey"); "defaultKey");
manager.setG2dRendererControl(g2dRendererControl);
manager.setG2dRenderer(g2dRenderer);
final Theme selected = theme("Selected"); final Theme selected = theme("Selected");
manager.setCurrentTheme(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")); assertTrue(manager.getAvailableThemeNames().contains("Selected"));
} }
@@ -94,7 +101,7 @@ public class G2DThemeManagerTest {
final StorageService storageService = org.mockito.Mockito.mock(StorageService.class); final StorageService storageService = org.mockito.Mockito.mock(StorageService.class);
final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.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()); .thenReturn(Optional.empty());
final Theme defaultTheme = theme("Default"); final Theme defaultTheme = theme("Default");
@@ -106,7 +113,7 @@ public class G2DThemeManagerTest {
"defaultKey"); "defaultKey");
final Theme loaded = theme("FromStorage"); 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)); .thenReturn(Optional.of(loaded));
final Optional<Theme> result = manager.loadThemeFromStorage("FromStorage"); final Optional<Theme> result = manager.loadThemeFromStorage("FromStorage");
@@ -121,7 +128,7 @@ public class G2DThemeManagerTest {
final StorageService storageService = org.mockito.Mockito.mock(StorageService.class); final StorageService storageService = org.mockito.Mockito.mock(StorageService.class);
final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.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()); .thenReturn(Optional.empty());
final Theme defaultTheme = theme("Default"); final Theme defaultTheme = theme("Default");
@@ -149,7 +156,7 @@ public class G2DThemeManagerTest {
final StorageService storageService = org.mockito.Mockito.mock(StorageService.class); final StorageService storageService = org.mockito.Mockito.mock(StorageService.class);
final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.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()); .thenReturn(Optional.empty());
final Theme defaultTheme = theme("Default"); final Theme defaultTheme = theme("Default");
@@ -163,7 +170,7 @@ public class G2DThemeManagerTest {
final Theme themeToSave = theme("UserTheme"); final Theme themeToSave = theme("UserTheme");
manager.saveThemeToStorage(themeToSave); 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 @Test
@@ -204,7 +211,7 @@ public class G2DThemeManagerTest {
final StorageService storageService = org.mockito.Mockito.mock(StorageService.class); final StorageService storageService = org.mockito.Mockito.mock(StorageService.class);
final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.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()); .thenReturn(Optional.empty());
final Theme defaultTheme = theme("Default"); final Theme defaultTheme = theme("Default");