From b05eb1055d0a47984e4c58ed2c7218f6b918afff Mon Sep 17 00:00:00 2001 From: Mario Date: Mon, 6 Jul 2026 07:15:14 +0200 Subject: [PATCH] Lokalisierungs-Editor: liest/schreibt messages_.properties in blight-lang TextBundleIO von JSON auf Java-Properties-Format umgestellt (messages_*.properties). Reihenfolge der Keys bleibt beim Laden erhalten. EditorApp zeigt jetzt auf blight-lang/src/main/resources/lang/ statt blight-assets/localization/. Co-Authored-By: Claude Sonnet 4.6 --- .../de/blight/common/model/TextBundleIO.java | 80 ++++++++++++++----- .../main/java/de/blight/editor/EditorApp.java | 3 +- 2 files changed, 61 insertions(+), 22 deletions(-) diff --git a/blight-common/src/main/java/de/blight/common/model/TextBundleIO.java b/blight-common/src/main/java/de/blight/common/model/TextBundleIO.java index 7447b54..5a23db3 100644 --- a/blight-common/src/main/java/de/blight/common/model/TextBundleIO.java +++ b/blight-common/src/main/java/de/blight/common/model/TextBundleIO.java @@ -1,13 +1,9 @@ package de.blight.common.model; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.lang.reflect.Type; +import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -15,31 +11,57 @@ import java.util.*; import java.util.stream.Stream; /** - * Lädt und speichert {@link TextBundle}-Instanzen als JSON. - * Dateiformat: {@code .json} im localization/-Verzeichnis. + * Lädt und speichert {@link TextBundle}-Instanzen als Java-Properties-Dateien. + * Dateiformat: {@code messages_.properties} im lang/-Verzeichnis. */ public final class TextBundleIO { private static final Logger log = LoggerFactory.getLogger(TextBundleIO.class); - private static final String EXTENSION = ".json"; - private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); - private static final Type MAP_TYPE = new TypeToken>(){}.getType(); + private static final String PREFIX = "messages_"; + private static final String EXTENSION = ".properties"; private TextBundleIO() {} public static void save(TextBundle bundle, Path dir) throws IOException { Files.createDirectories(dir); - Files.writeString(dir.resolve(bundle.getLanguage() + EXTENSION), - GSON.toJson(bundle.getEntries()), StandardCharsets.UTF_8); - log.debug("[TextBundleIO] Gespeichert: {}", bundle.getLanguage()); + Path file = dir.resolve(PREFIX + bundle.getLanguage() + EXTENSION); + Properties props = new Properties(); + props.putAll(bundle.getEntries()); + try (BufferedWriter w = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) { + // Manuell schreiben um Kommentar-Header und Reihenfolge zu erhalten + for (Map.Entry e : bundle.getEntries().entrySet()) { + w.write(escapeKey(e.getKey()) + "=" + escapeValue(e.getValue())); + w.newLine(); + } + } + log.debug("[TextBundleIO] Gespeichert: {}", file); } public static TextBundle load(Path file) throws IOException { - String lang = file.getFileName().toString().replace(EXTENSION, ""); - Map map = GSON.fromJson( - Files.readString(file, StandardCharsets.UTF_8), MAP_TYPE); + String name = file.getFileName().toString(); + String lang = name.replace(PREFIX, "").replace(EXTENSION, ""); + Properties props = new Properties(); + try (BufferedReader r = Files.newBufferedReader(file, StandardCharsets.UTF_8)) { + props.load(r); + } + // Properties erhält keine garantierte Reihenfolge – beim Laden Reihenfolge aus Datei rekonstruieren + LinkedHashMap ordered = new LinkedHashMap<>(); + try (BufferedReader r = Files.newBufferedReader(file, StandardCharsets.UTF_8)) { + String line; + while ((line = r.readLine()) != null) { + line = line.trim(); + if (line.isEmpty() || line.startsWith("#") || line.startsWith("!")) continue; + int eq = line.indexOf('='); + int colon = line.indexOf(':'); + int sep = (eq >= 0 && (colon < 0 || eq <= colon)) ? eq : colon; + if (sep < 0) continue; + String key = line.substring(0, sep).trim(); + String val = props.getProperty(key, ""); + ordered.put(key, val); + } + } TextBundle bundle = new TextBundle(lang); - if (map != null) bundle.setEntries(new LinkedHashMap<>(map)); + bundle.setEntries(ordered); return bundle; } @@ -47,7 +69,10 @@ public final class TextBundleIO { List result = new ArrayList<>(); if (!Files.isDirectory(dir)) return result; try (Stream walk = Files.list(dir)) { - walk.filter(p -> p.toString().endsWith(EXTENSION)) + walk.filter(p -> { + String n = p.getFileName().toString(); + return n.startsWith(PREFIX) && n.endsWith(EXTENSION); + }) .sorted() .forEach(p -> { try { result.add(load(p)); } @@ -63,8 +88,11 @@ public final class TextBundleIO { List langs = new ArrayList<>(); if (!Files.isDirectory(dir)) return langs; try (Stream walk = Files.list(dir)) { - walk.filter(p -> p.toString().endsWith(EXTENSION)) - .map(p -> p.getFileName().toString().replace(EXTENSION, "")) + walk.filter(p -> { + String n = p.getFileName().toString(); + return n.startsWith(PREFIX) && n.endsWith(EXTENSION); + }) + .map(p -> p.getFileName().toString().replace(PREFIX, "").replace(EXTENSION, "")) .sorted() .forEach(langs::add); } catch (IOException ignored) {} @@ -72,6 +100,16 @@ public final class TextBundleIO { } public static void delete(String language, Path dir) throws IOException { - Files.deleteIfExists(dir.resolve(language + EXTENSION)); + Files.deleteIfExists(dir.resolve(PREFIX + language + EXTENSION)); + } + + // ── Hilfsmethoden ───────────────────────────────────────────────────────── + + private static String escapeKey(String key) { + return key.replace(" ", "\\ "); + } + + private static String escapeValue(String val) { + return val.replace("\\", "\\\\").replace("\n", "\\n").replace("\r", "\\r"); } } diff --git a/blight-editor/src/main/java/de/blight/editor/EditorApp.java b/blight-editor/src/main/java/de/blight/editor/EditorApp.java index edc02fd..eb65853 100644 --- a/blight-editor/src/main/java/de/blight/editor/EditorApp.java +++ b/blight-editor/src/main/java/de/blight/editor/EditorApp.java @@ -10249,7 +10249,8 @@ public class EditorApp extends Application { label.setStyle("-fx-font-weight: bold; -fx-font-size: 13;"); tb.getItems().addAll(backBtn, new Separator(Orientation.VERTICAL), label); topBar.getChildren().set(1, tb); - root.setCenter(new de.blight.editor.ui.LocalizationEditorView(ASSET_ROOT.resolve("localization"))); + root.setCenter(new de.blight.editor.ui.LocalizationEditorView( + de.blight.editor.ProjectRoot.resolve("blight-lang", "src", "main", "resources", "lang"))); root.setRight(null); }