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;
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 <lang>.json} im localization/-Verzeichnis.
* Lädt und speichert {@link TextBundle}-Instanzen als Java-Properties-Dateien.
* Dateiformat: {@code messages_<lang>.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<LinkedHashMap<String, String>>(){}.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<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 {
String lang = file.getFileName().toString().replace(EXTENSION, "");
Map<String, String> 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<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);
if (map != null) bundle.setEntries(new LinkedHashMap<>(map));
bundle.setEntries(ordered);
return bundle;
}
@@ -47,7 +69,10 @@ public final class TextBundleIO {
List<TextBundle> result = new ArrayList<>();
if (!Files.isDirectory(dir)) return result;
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()
.forEach(p -> {
try { result.add(load(p)); }
@@ -63,8 +88,11 @@ public final class TextBundleIO {
List<String> langs = new ArrayList<>();
if (!Files.isDirectory(dir)) return langs;
try (Stream<Path> 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");
}
}