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:
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user