Lokalisierungs-Editor: liest/schreibt messages_<lang>.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 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 07:15:14 +02:00
parent 06bb955c78
commit b05eb1055d
2 changed files with 61 additions and 22 deletions

View File

@@ -1,13 +1,9 @@
package de.blight.common.model; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.*;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -15,31 +11,57 @@ import java.util.*;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**
* Lädt und speichert {@link TextBundle}-Instanzen als JSON. * Lädt und speichert {@link TextBundle}-Instanzen als Java-Properties-Dateien.
* Dateiformat: {@code <lang>.json} im localization/-Verzeichnis. * Dateiformat: {@code messages_<lang>.properties} im lang/-Verzeichnis.
*/ */
public final class TextBundleIO { public final class TextBundleIO {
private static final Logger log = LoggerFactory.getLogger(TextBundleIO.class); private static final Logger log = LoggerFactory.getLogger(TextBundleIO.class);
private static final String EXTENSION = ".json"; private static final String PREFIX = "messages_";
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); private static final String EXTENSION = ".properties";
private static final Type MAP_TYPE = new TypeToken<LinkedHashMap<String, String>>(){}.getType();
private TextBundleIO() {} private TextBundleIO() {}
public static void save(TextBundle bundle, Path dir) throws IOException { public static void save(TextBundle bundle, Path dir) throws IOException {
Files.createDirectories(dir); Files.createDirectories(dir);
Files.writeString(dir.resolve(bundle.getLanguage() + EXTENSION), Path file = dir.resolve(PREFIX + bundle.getLanguage() + EXTENSION);
GSON.toJson(bundle.getEntries()), StandardCharsets.UTF_8); Properties props = new Properties();
log.debug("[TextBundleIO] Gespeichert: {}", bundle.getLanguage()); 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<String, String> 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 { public static TextBundle load(Path file) throws IOException {
String lang = file.getFileName().toString().replace(EXTENSION, ""); String name = file.getFileName().toString();
Map<String, String> map = GSON.fromJson( String lang = name.replace(PREFIX, "").replace(EXTENSION, "");
Files.readString(file, StandardCharsets.UTF_8), MAP_TYPE); 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<String, String> 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); TextBundle bundle = new TextBundle(lang);
if (map != null) bundle.setEntries(new LinkedHashMap<>(map)); bundle.setEntries(ordered);
return bundle; return bundle;
} }
@@ -47,7 +69,10 @@ public final class TextBundleIO {
List<TextBundle> result = new ArrayList<>(); List<TextBundle> result = new ArrayList<>();
if (!Files.isDirectory(dir)) return result; if (!Files.isDirectory(dir)) return result;
try (Stream<Path> walk = Files.list(dir)) { try (Stream<Path> 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() .sorted()
.forEach(p -> { .forEach(p -> {
try { result.add(load(p)); } try { result.add(load(p)); }
@@ -63,8 +88,11 @@ public final class TextBundleIO {
List<String> langs = new ArrayList<>(); List<String> langs = new ArrayList<>();
if (!Files.isDirectory(dir)) return langs; if (!Files.isDirectory(dir)) return langs;
try (Stream<Path> walk = Files.list(dir)) { try (Stream<Path> walk = Files.list(dir)) {
walk.filter(p -> p.toString().endsWith(EXTENSION)) walk.filter(p -> {
.map(p -> p.getFileName().toString().replace(EXTENSION, "")) String n = p.getFileName().toString();
return n.startsWith(PREFIX) && n.endsWith(EXTENSION);
})
.map(p -> p.getFileName().toString().replace(PREFIX, "").replace(EXTENSION, ""))
.sorted() .sorted()
.forEach(langs::add); .forEach(langs::add);
} catch (IOException ignored) {} } catch (IOException ignored) {}
@@ -72,6 +100,16 @@ public final class TextBundleIO {
} }
public static void delete(String language, Path dir) throws IOException { 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");
} }
} }

View File

@@ -10249,7 +10249,8 @@ public class EditorApp extends Application {
label.setStyle("-fx-font-weight: bold; -fx-font-size: 13;"); label.setStyle("-fx-font-weight: bold; -fx-font-size: 13;");
tb.getItems().addAll(backBtn, new Separator(Orientation.VERTICAL), label); tb.getItems().addAll(backBtn, new Separator(Orientation.VERTICAL), label);
topBar.getChildren().set(1, tb); 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); root.setRight(null);
} }