feat(i18n): Add i18n configuration and resource management with localization support

feat(renderer): Integrate StaticResourceService for dynamic resource loading in renderer
feat(font): Implement font resource management and update default font handling
feat(resources): Add icon and font files for renderer and localization features
This commit is contained in:
extio-de
2025-12-24 16:25:18 +01:00
parent 4633527aa9
commit 1bc116374c
14 changed files with 190 additions and 45 deletions

View File

@@ -1,5 +1,10 @@
package de.extio.game_engine.i18n;
import java.io.File;
import java.net.URI;
import java.nio.file.Path;
import java.util.stream.StreamSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
@@ -8,28 +13,33 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import de.extio.game_engine.resource.StaticResource;
import de.extio.game_engine.resource.StaticResourceService;
@AutoConfiguration
@ConditionalOnProperty(name = "game-engine.i18n.enabled", havingValue = "true", matchIfMissing = true)
public class LocalizationAutoConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(LocalizationServiceImpl.class);
@Bean
@ConditionalOnMissingBean
LocalizationService localizationManager(@Value("${game-engine.i18n.load-on-start:true}") final boolean loadOnStart, @Value("${game-engine.i18n.resource:i18n.yaml}") final String resource) {
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();
if (loadOnStart) {
try (var resourceStream = LocalizationAutoConfiguration.class.getClassLoader().getResourceAsStream(resource)) {
if (resourceStream != null) {
localizationManager.load(resourceStream);
final var path = new File(resource).toPath();
final var staticResource = new StaticResource(path.getParent() != null ? StreamSupport.stream(path.getParent().spliterator(), false).map(Path::toString).toList() : null, path.getFileName().toString());
staticResourceService.loadStreamByPath(staticResource).ifPresent(stream -> {
try (stream) {
localizationManager.load(stream);
}
}
catch (final Exception exc) {
LOGGER.error("An exception occured while loading localizations from resource: " + resource, exc);
}
catch (final Exception e) {
LOGGER.error("An exception occured while loading localizations from resource: " + resource, e);
}
});
}
return localizationManager;
}

View File

@@ -118,12 +118,12 @@ public class LocalizationEditor extends JFrame {
public void run() {
try {
final LocalizationEditor frame = new LocalizationEditor();
try (InputStream in = frame.getClass().getClassLoader().getResourceAsStream("icon.png")) {
final BufferedImage icon = ImageIO.read(in);
frame.setIconImage(icon);
}
catch (final Exception e) {
}
// try (InputStream in = frame.getClass().getClassLoader().getResourceAsStream("icon.png")) {
// final BufferedImage icon = ImageIO.read(in);
// frame.setIconImage(icon);
// }
// catch (final Exception e) {
// }
frame.setVisible(true);
frame.localizationManagerImpl.reset();

View File

@@ -3,9 +3,7 @@ package de.extio.game_engine.renderer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -21,6 +19,8 @@ import de.extio.game_engine.renderer.work.RendererWorkingSet;
import de.extio.game_engine.renderer.work.RendererWorkingSetImpl;
import de.extio.game_engine.renderer.work.RenderingBoPool;
import de.extio.game_engine.renderer.work.RenderingBoPoolImpl;
import de.extio.game_engine.resource.StaticResourceService;
import de.extio.game_engine.resource.StaticResourceService;
import de.extio.game_engine.storage.StorageService;
@AutoConfiguration
@@ -59,9 +59,10 @@ public class RendererAutoConfiguration {
final StorageService storageService,
final RendererWorkingSet rendererWorkingSet,
final ModuleService moduleManager,
final ModuleExecutor moduleExecutor) {
final ModuleExecutor moduleExecutor,
final StaticResourceService staticResourceService) {
final var rendererData = new RendererData(applicationContext, renderer, rendererControl, renderingBoPool, keycodeRegistry, eventService, storageService, rendererWorkingSet, moduleManager, moduleExecutor);
final var rendererData = new RendererData(applicationContext, renderer, rendererControl, renderingBoPool, keycodeRegistry, eventService, storageService, rendererWorkingSet, moduleManager, moduleExecutor, staticResourceService);
renderer.setRendererData(rendererData);
rendererControl.setRendererData(rendererData);
renderingBoPool.setRendererData(rendererData);

View File

@@ -13,6 +13,7 @@ import de.extio.game_engine.renderer.model.options.UiOptions;
import de.extio.game_engine.renderer.model.options.VideoOptions;
import de.extio.game_engine.renderer.work.RendererWorkingSet;
import de.extio.game_engine.renderer.work.RenderingBoPool;
import de.extio.game_engine.resource.StaticResourceService;
import de.extio.game_engine.storage.StorageService;
public class RendererData {
@@ -42,10 +43,12 @@ public class RendererData {
private final ModuleService moduleManager;
private final ModuleExecutor moduleExecutor;
private final StaticResourceService staticResourceService;
private RendererLoop rendererLoop;
public RendererData(final ApplicationContext applicationContext, final Renderer renderer, final RendererControl rendererControl, final RenderingBoPool renderingBoPool, final KeycodeRegistry keycodeRegistry, final EventService eventService, final StorageService storageService, final RendererWorkingSet rendererWorkingSet, final ModuleService moduleManager, final ModuleExecutor moduleExecutor) {
public RendererData(final ApplicationContext applicationContext, final Renderer renderer, final RendererControl rendererControl, final RenderingBoPool renderingBoPool, final KeycodeRegistry keycodeRegistry, final EventService eventService, final StorageService storageService, final RendererWorkingSet rendererWorkingSet, final ModuleService moduleManager, final ModuleExecutor moduleExecutor, final StaticResourceService staticResourceService) {
this.applicationContext = applicationContext;
this.renderer = renderer;
this.rendererControl = rendererControl;
@@ -56,6 +59,7 @@ public class RendererData {
this.rendererWorkingSet = rendererWorkingSet;
this.moduleManager = moduleManager;
this.moduleExecutor = moduleExecutor;
this.staticResourceService = staticResourceService;
}
public ApplicationContext getApplicationContext() {
@@ -121,4 +125,8 @@ public class RendererData {
public ModuleExecutor getModuleExecutor() {
return moduleExecutor;
}
public StaticResourceService getStaticResourceService() {
return staticResourceService;
}
}

View File

@@ -22,6 +22,7 @@ import java.awt.event.MouseWheelListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import javax.imageio.ImageIO;
@@ -63,15 +64,15 @@ public class G2DMainFrame extends Frame {
this.setLayout(null);
this.setBackground(Color.BLACK);
try (var in = this.getClass().getClassLoader().getResourceAsStream("icon.png")) {
if (in != null) {
this.rendererData.getStaticResourceService().loadStreamByPath(List.of("renderer"), "icon.png").ifPresent(stream -> {
try (var in = stream) {
final var icon = ImageIO.read(in);
this.setIconImage(icon);
}
}
catch (final IOException e) {
LOGGER.error(e.getMessage(), e);
}
catch (final IOException e) {
LOGGER.warn(e.getMessage(), e);
}
});
this.addWindowListener(new WindowListener() {

View File

@@ -2,6 +2,7 @@ package de.extio.game_engine.renderer.g2d;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
@@ -9,7 +10,9 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -22,8 +25,10 @@ import org.slf4j.LoggerFactory;
import de.extio.game_engine.module.AbstractClientModule;
import de.extio.game_engine.renderer.Renderer;
import de.extio.game_engine.renderer.RendererData;
import de.extio.game_engine.renderer.g2d.G2DRenderer.G2DRendererModule;
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DAbstractRenderingBo;
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawBackground;
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont;
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFpsHistory;
import de.extio.game_engine.renderer.g2d.control.G2DDrawControl;
import de.extio.game_engine.renderer.g2d.control.G2DDrawControlTooltip;
@@ -32,6 +37,7 @@ import de.extio.game_engine.renderer.model.RenderingBoLayer;
import de.extio.game_engine.renderer.model.bo.DrawFontRenderingBo;
import de.extio.game_engine.renderer.model.color.RgbaColor;
import de.extio.game_engine.renderer.model.event.TakeScreenshotEvent;
import de.extio.game_engine.resource.StaticResource;
import de.extio.game_engine.util.RingBuffer;
public class G2DRenderer implements Renderer {
@@ -65,6 +71,8 @@ public class G2DRenderer implements Renderer {
private String title;
private volatile boolean takeScreenshot;
private StaticResource previousDefaultFont = null;
public G2DRenderer() {
LOGGER.info("ctor");
@@ -140,11 +148,15 @@ public class G2DRenderer implements Renderer {
}
final var viewPortDimension = this.rendererData.getRendererControl().getAbsoluteViewportDimension();
if (viewPortDimension.getX() < 10 || viewPortDimension.getY() < 10) {
// Some race condition happens rarely (mainly Windows 11) that window size is not initialized after entering full screen. Put a plaster on it...
throw new G2DRendererWindowNotInitializedException();
}
if (this.previousDefaultFont == null && this.rendererData.getUiOptions().getFontResource() != null && !this.rendererData.getUiOptions().getFontResource().equals(this.previousDefaultFont)) {
this.previousDefaultFont = this.rendererData.getUiOptions().getFontResource();
G2DDrawFont.updateDefaultFont(this.rendererData.getStaticResourceService(), this.rendererData.getUiOptions().getFontResource());
}
BufferedImage screenshotImg = null;
Graphics2D screenshotGraphics = null;

View File

@@ -162,6 +162,7 @@ public class G2DRendererControl implements RendererControl {
.store(List.of("gameEngine"), "g2dRendererControlOptions", this.options);
this.recalculate();
this.rendererData.getEventService().fire(new WindowResizeEvent());
}
@Override

View File

@@ -15,6 +15,8 @@ import de.extio.game_engine.renderer.g2d.G2DRendererCondition;
import de.extio.game_engine.renderer.model.RenderingBoLayer;
import de.extio.game_engine.renderer.model.bo.DrawFontRenderingBo;
import de.extio.game_engine.renderer.model.bo.DrawFontRenderingBoTextAlignment;
import de.extio.game_engine.resource.StaticResource;
import de.extio.game_engine.resource.StaticResourceService;
import de.extio.game_engine.spatial2.model.CoordI2;
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
import de.extio.game_engine.spatial2.model.MutableCoordI2;
@@ -27,20 +29,25 @@ public class G2DDrawFont extends G2DAbstractRenderingBo implements DrawFontRende
private final static int FONT_SIZE_DEFAULT = 14;
private final static Font baseFont;
private static Font baseFont;
static {
Font f;
try {
f = Font.createFont(Font.TRUETYPE_FONT, ClassLoader.getSystemClassLoader().getResourceAsStream("Roboto-Regular.ttf"));
}
catch (final Exception e) {
final Map<TextAttribute, Object> attributes = new HashMap<>();
attributes.put(TextAttribute.FAMILY, Font.SANS_SERIF);
attributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_SEMIBOLD);
f = Font.getFont(attributes);
}
baseFont = f;
final Map<TextAttribute, Object> attributes = new HashMap<>();
attributes.put(TextAttribute.FAMILY, Font.SANS_SERIF);
attributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_SEMIBOLD);
baseFont = Font.getFont(attributes);
}
public static void updateDefaultFont(final StaticResourceService staticResourceService, final StaticResource resource) {
staticResourceService.loadStreamByPath(resource).ifPresent(stream -> {
try (var in = stream) {
final var font = Font.createFont(Font.TRUETYPE_FONT, in);
G2DDrawFont.baseFont = font;
}
catch (final Exception e) {
LOGGER.warn(e.getMessage(), e);
}
});
}
private String text;

View File

@@ -1,10 +1,15 @@
package de.extio.game_engine.renderer.model.options;
import java.util.List;
import de.extio.game_engine.resource.StaticResource;
import de.extio.game_engine.spatial2.model.CoordI2;
import de.extio.game_engine.spatial2.model.MutableCoordI2;
public final class UiOptions {
private boolean drawFps = true;
private String backgroundResourceName0 = null;
private boolean backgroundScrolling0;
@@ -15,12 +20,12 @@ public final class UiOptions {
private boolean backgroundScrollingReverse1;
private boolean drawFps = true;
private CoordI2 backgroundOffset0 = MutableCoordI2.create();
private CoordI2 backgroundOffset1 = MutableCoordI2.create();
private StaticResource fontResource = new StaticResource(List.of("renderer"), "Inter_24pt-Regular.ttf");
public UiOptions() {
if ("true".equals(System.getProperty("debug"))) {
this.drawFps = true;
@@ -91,4 +96,11 @@ public final class UiOptions {
this.backgroundOffset1 = backgroundOffset1;
}
public StaticResource getFontResource() {
return fontResource;
}
public void setFontResource(final StaticResource fontResource) {
this.fontResource = fontResource;
}
}

View File

@@ -3,6 +3,6 @@ de.extio.game_engine.keyboard.KeycodeAutoConfiguration
de.extio.game_engine.renderer.RendererAutoConfiguration
de.extio.game_engine.renderer.g2d.G2DAutoConfiguration
de.extio.game_engine.storage.StorageServiceAutoConfiguration
de.extio.game_engine.module.ModuleManagerAutoConfiguration
de.extio.game_engine.module.ModuleServiceAutoConfiguration
de.extio.game_engine.i18n.LocalizationAutoConfiguration
de.extio.game_engine.resource.StaticResourceAutoConfiguration

View File

@@ -1,20 +0,0 @@
---
curId: 2
descriptions:
test-2: ""
test-1: "aaa"
languages:
en:
test-1: "hello"
test-2: "world"
de:
test-1: "hallo"
test-2: "welt"
languagesInfo:
en:
name: "English"
shortName: "en"
de:
name: "Deutsch"
shortName: "de"
prefix: "test"