Lokalisierungseditor wieder in den Editor integriert und Fehler behoben, Funktionsumfang ergänzt

This commit is contained in:
2026-08-22 21:39:13 +02:00
parent d0f33a5b4b
commit fdd8eb60be
3 changed files with 147 additions and 42 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

View File

@@ -96,7 +96,7 @@ public class EditorApp extends Application {
private String contentLastSection = "character";
private ToggleButton contentCharBtn, contentQuestBtn, contentFraktionBtn,
contentItemBtn, contentRezeptBtn, contentCraftingBtn,
contentAreaBtn, contentLocationBtn;
contentAreaBtn, contentLocationBtn, contentLangBtn;
private CheckMenuItem dualWindowMenuItem;
private CheckMenuItem showGridItem;
private Button contentSwitchBtn; // "> Content" in Welt-Toolbar (nur 1-Fenster)
@@ -9642,7 +9642,13 @@ public class EditorApp extends Application {
else if (input.ctrlHeld) input.debugNoLightToggle = true;
} else if (input.ctrlHeld) input.debugNoLightToggle = true;
}
case F9 -> { if (pressed && "world".equals(currentTool)) Platform.runLater(() -> { if (wasserMergedBtn != null) wasserMergedBtn.fire(); }); }
case F9 -> {
if (pressed && input.ctrlHeld) Platform.runLater(() -> {
if (!dualWindowMode && !"content".equals(currentTool)) switchToContentEditor();
if (contentLangBtn != null) contentLangBtn.fire();
});
else if (pressed && "world".equals(currentTool)) Platform.runLater(() -> { if (wasserMergedBtn != null) wasserMergedBtn.fire(); });
}
case F10 -> { if (pressed && "world".equals(currentTool)) Platform.runLater(() -> { if (wegeBtn != null) wegeBtn.fire(); }); }
case F11 -> { if (pressed && "world".equals(currentTool)) Platform.runLater(() -> { if (zonenBtn != null) zonenBtn.fire(); }); }
case F12 -> { if (pressed && !input.ctrlHeld && "world".equals(currentTool)) Platform.runLater(() -> { if (playToolBtn != null) playToolBtn.fire(); }); }
@@ -12959,10 +12965,12 @@ public class EditorApp extends Application {
contentCraftingBtn = new ToggleButton("# Crafting");
contentAreaBtn = new ToggleButton("□ Area");
contentLocationBtn = new ToggleButton("^ Location");
contentLangBtn = new ToggleButton("≈ Lang");
String bold = "-fx-font-weight:bold;";
for (ToggleButton b : new ToggleButton[]{contentCharBtn, contentQuestBtn, contentFraktionBtn,
contentItemBtn, contentRezeptBtn, contentCraftingBtn, contentAreaBtn, contentLocationBtn}) {
contentItemBtn, contentRezeptBtn, contentCraftingBtn, contentAreaBtn, contentLocationBtn,
contentLangBtn}) {
b.setStyle(bold);
}
@@ -12975,6 +12983,7 @@ public class EditorApp extends Application {
contentCraftingBtn.setToggleGroup(cg);
contentAreaBtn.setToggleGroup(cg);
contentLocationBtn.setToggleGroup(cg);
contentLangBtn.setToggleGroup(cg);
contentCharBtn.setOnAction(e -> {
contentLastSection = "character"; buildCharacterViewInContent();
@@ -13000,6 +13009,9 @@ public class EditorApp extends Application {
contentLocationBtn.setOnAction(e -> {
contentLastSection = "location"; buildLocationViewInContent();
});
contentLangBtn.setOnAction(e -> {
contentLastSection = "lang"; buildLocalizationViewInContent();
});
ToolBar tb = new ToolBar();
tb.getItems().addAll(
@@ -13008,7 +13020,8 @@ public class EditorApp extends Application {
new Separator(Orientation.VERTICAL), contentFraktionBtn,
new Separator(Orientation.VERTICAL), contentItemBtn,
new Separator(Orientation.VERTICAL), contentRezeptBtn, contentCraftingBtn,
new Separator(Orientation.VERTICAL), contentAreaBtn, contentLocationBtn);
new Separator(Orientation.VERTICAL), contentAreaBtn, contentLocationBtn,
new Separator(Orientation.VERTICAL), contentLangBtn);
if (includeWeltBtn) {
Button weltBtn = new Button("< Welt");
weltBtn.setStyle("-fx-font-weight:bold;");
@@ -13443,6 +13456,7 @@ public class EditorApp extends Application {
case "crafting" -> contentCraftingBtn.fire();
case "area" -> contentAreaBtn.fire();
case "location" -> contentLocationBtn.fire();
case "lang" -> contentLangBtn.fire();
}
}
@@ -13574,6 +13588,15 @@ public class EditorApp extends Application {
if (contentLocationNode != null) contentLocationNode.setExpanded(true);
}
private void buildLocalizationViewInContent() {
collapseAllContentSections();
onF5 = null;
contentTarget().setCenter(new de.blight.editor.ui.LocalizationEditorView(
de.blight.editor.ProjectRoot.resolve("blight-lang", "src", "main", "resources", "lang")));
contentTarget().setRight(null);
contentTarget().setBottom(null);
}
// ── Baum-Refresh-Methoden ─────────────────────────────────────────────────
private void refreshContentQuestTree() {

View File

@@ -8,7 +8,6 @@ import de.blight.common.model.TextKeyStore;
import de.blight.common.model.TextRegistry;
import de.blight.editor.ProjectRoot;
import javafx.animation.PauseTransition;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
@@ -24,6 +23,7 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
public class LocalizationEditorView extends BorderPane {
@@ -33,10 +33,14 @@ public class LocalizationEditorView extends BorderPane {
private final ComboBox<String> langCombo = new ComboBox<>();
// Text-Tab
private final ObservableList<String[]> tableData = FXCollections.observableArrayList();
private final TableView<String[]> table = new TableView<>(tableData);
private final ObservableList<String[]> tableData = FXCollections.observableArrayList();
private final FilteredList<String[]> filteredData = new FilteredList<>(tableData, s -> true);
private final TableView<String[]> table = new TableView<>(filteredData);
private TextBundle currentBundle = null;
// Cache bekannter Keys für Row-Highlighting (aktualisiert in loadLanguage)
private Set<String> knownKeys = new HashSet<>();
// Audio-Tab
private final ObservableList<String[]> audioData = FXCollections.observableArrayList();
private final TableView<String[]> audioTable = new TableView<>(audioData);
@@ -59,7 +63,7 @@ public class LocalizationEditorView extends BorderPane {
Label langLbl = new Label("Sprache:");
langLbl.setStyle("-fx-text-fill: #aaa;");
langCombo.setPrefWidth(120);
langCombo.setOnAction(e -> loadLanguage(langCombo.getValue()));
langCombo.valueProperty().addListener((obs, o, n) -> loadLanguage(n));
Button addLangBtn = new Button("+ Sprache");
addLangBtn.setOnAction(e -> addLanguage());
@@ -87,43 +91,87 @@ public class LocalizationEditorView extends BorderPane {
// Text-Tab
private VBox buildTextTabContent() {
Button addKeyBtn = new Button("+ Schluessel");
// ── Filter ────────────────────────────────────────────────────────────
TextField filterField = new TextField();
filterField.setPromptText("Filtern (Schlüssel oder Text)…");
filterField.setStyle("-fx-background-color: #252535; -fx-text-fill: #ddd; -fx-prompt-text-fill: #666;" +
"-fx-border-color: #444; -fx-border-width: 1; -fx-background-radius: 3; -fx-border-radius: 3;");
filterField.textProperty().addListener((obs, o, n) -> {
String q = n == null ? "" : n.toLowerCase();
filteredData.setPredicate(row ->
q.isBlank()
|| row[0].toLowerCase().contains(q)
|| row[1].toLowerCase().contains(q));
});
HBox filterBar = new HBox(6, new Label(" "), filterField);
HBox.setHgrow(filterField, Priority.ALWAYS);
filterBar.setPadding(new Insets(5, 12, 5, 12));
filterBar.setAlignment(Pos.CENTER_LEFT);
filterBar.setStyle("-fx-background-color: #1a1a2a; -fx-border-color: #444; -fx-border-width: 0 0 1 0;");
Label filterLbl = new Label("Filter:");
filterLbl.setStyle("-fx-text-fill: #aaa;");
filterBar.getChildren().set(0, filterLbl);
// ── Button-Leiste ─────────────────────────────────────────────────────
Button addKeyBtn = new Button("+ Schlüssel");
addKeyBtn.setOnAction(e -> addKey());
Button delKeyBtn = new Button("- Entfernen");
delKeyBtn.setOnAction(e -> deleteKey());
Button sortBtn = new Button("A->Z");
Button sortBtn = new Button("AZ");
sortBtn.setOnAction(e -> tableData.sort(Comparator.comparing(r -> r[0])));
HBox subBar = new HBox(6, addKeyBtn, delKeyBtn, sortBtn);
Button fillBtn = new Button("Fehlende ergänzen");
fillBtn.setStyle("-fx-background-color: #2a4a2a; -fx-text-fill: #ccffcc;");
fillBtn.setOnAction(e -> fillMissingKeys());
HBox subBar = new HBox(6, addKeyBtn, delKeyBtn, sortBtn, new Separator(), fillBtn);
subBar.setPadding(new Insets(6, 12, 6, 12));
subBar.setAlignment(Pos.CENTER_LEFT);
subBar.setStyle("-fx-background-color: #1a1a2a; -fx-border-color: #444; -fx-border-width: 0 0 1 0;");
// ── Tabelle ───────────────────────────────────────────────────────────
table.setEditable(true);
table.setStyle("-fx-background-color: #1a1a2a; -fx-control-inner-background: #1a1a2a;");
@SuppressWarnings("deprecation")
var policy = TableView.CONSTRAINED_RESIZE_POLICY;
table.setColumnResizePolicy(policy);
TableColumn<String[], String> keyCol = new TableColumn<>("Schluessel");
keyCol.setCellValueFactory(cd -> new SimpleStringProperty(cd.getValue()[0]));
keyCol.setCellFactory(TextFieldTableCell.forTableColumn());
keyCol.setOnEditCommit(e -> { e.getRowValue()[0] = e.getNewValue().trim(); table.refresh(); scheduleSave(); });
// Schlüssel: read-only
TableColumn<String[], String> keyCol = new TableColumn<>("Schlüssel");
keyCol.setCellValueFactory(cd -> new javafx.beans.property.SimpleStringProperty(cd.getValue()[0]));
keyCol.setEditable(false);
keyCol.setPrefWidth(280);
// Text: editierbar
TableColumn<String[], String> valCol = new TableColumn<>("Text");
valCol.setCellValueFactory(cd -> new SimpleStringProperty(cd.getValue()[1]));
valCol.setCellValueFactory(cd -> new javafx.beans.property.SimpleStringProperty(cd.getValue()[1]));
valCol.setCellFactory(TextFieldTableCell.forTableColumn());
valCol.setOnEditCommit(e -> { e.getRowValue()[1] = e.getNewValue(); table.refresh(); scheduleSave(); });
table.getColumns().addAll(List.of(keyCol, valCol));
// Row-Factory: unbekannte Schlüssel orange hervorheben
table.setRowFactory(tv -> new TableRow<>() {
@Override
protected void updateItem(String[] item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null || item[0].isBlank()) {
setStyle("");
} else {
setStyle(knownKeys.contains(item[0]) ? "" : "-fx-background-color: #3a2510;");
}
}
});
VBox.setVgrow(table, Priority.ALWAYS);
Label hint = new Label("Doppelklick zum Bearbeiten eines Eintrags.");
Label hint = new Label("Schlüssel (orange = nicht in TextKeyStore registriert). Doppelklick auf Text zum Bearbeiten.");
hint.setStyle("-fx-font-size: 10; -fx-text-fill: #666; -fx-padding: 2 12 4 12;");
VBox box = new VBox(subBar, table, hint);
VBox box = new VBox(filterBar, subBar, table, hint);
VBox.setVgrow(table, Priority.ALWAYS);
return box;
}
@@ -131,16 +179,16 @@ public class LocalizationEditorView extends BorderPane {
// Audio-Tab
private VBox buildAudioTabContent() {
Button addKeyBtn = new Button("+ Schluessel");
Button addKeyBtn = new Button("+ Schlüssel");
addKeyBtn.setOnAction(e -> addAudioKey());
Button delKeyBtn = new Button("- Entfernen");
delKeyBtn.setOnAction(e -> deleteAudioKey());
Button sortBtn = new Button("A->Z");
Button sortBtn = new Button("AZ");
sortBtn.setOnAction(e -> audioData.sort(Comparator.comparing(r -> r[0])));
Button fillDialogBtn = new Button("Dialog-Schluessel befuellen");
Button fillDialogBtn = new Button("Dialog-Schlüssel befüllen");
fillDialogBtn.setStyle("-fx-background-color: #3a4a6a; -fx-text-fill: white;");
fillDialogBtn.setOnAction(e -> autoFillDialogKeys());
@@ -154,14 +202,14 @@ public class LocalizationEditorView extends BorderPane {
var policy2 = TableView.CONSTRAINED_RESIZE_POLICY;
audioTable.setColumnResizePolicy(policy2);
TableColumn<String[], String> keyCol = new TableColumn<>("Schluessel");
keyCol.setCellValueFactory(cd -> new SimpleStringProperty(cd.getValue()[0]));
TableColumn<String[], String> keyCol = new TableColumn<>("Schlüssel");
keyCol.setCellValueFactory(cd -> new javafx.beans.property.SimpleStringProperty(cd.getValue()[0]));
keyCol.setCellFactory(TextFieldTableCell.forTableColumn());
keyCol.setOnEditCommit(e -> { e.getRowValue()[0] = e.getNewValue().trim(); audioTable.refresh(); scheduleSave(); });
keyCol.setPrefWidth(220);
TableColumn<String[], String> pathCol = new TableColumn<>("Datei");
pathCol.setCellValueFactory(cd -> new SimpleStringProperty(cd.getValue()[1]));
pathCol.setCellValueFactory(cd -> new javafx.beans.property.SimpleStringProperty(cd.getValue()[1]));
pathCol.setCellFactory(TextFieldTableCell.forTableColumn());
pathCol.setOnEditCommit(e -> { e.getRowValue()[1] = e.getNewValue(); audioTable.refresh(); scheduleSave(); });
@@ -177,7 +225,7 @@ public class LocalizationEditorView extends BorderPane {
int idx = getIndex();
if (idx < 0 || idx >= audioData.size()) return;
FileChooser fc = new FileChooser();
fc.setTitle("Audio-Datei waehlen");
fc.setTitle("Audio-Datei wählen");
fc.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter("Audio", "*.ogg", "*.wav", "*.mp3"),
new FileChooser.ExtensionFilter("Alle Dateien", "*.*")
@@ -211,7 +259,7 @@ public class LocalizationEditorView extends BorderPane {
audioTable.getColumns().addAll(List.of(keyCol, pathCol, browseCol));
VBox.setVgrow(audioTable, Priority.ALWAYS);
Label hint = new Label("Schluessel muss dem AudioReference-Key im Dialog entsprechen.");
Label hint = new Label("Schlüssel muss dem AudioReference-Key im Dialog entsprechen.");
hint.setStyle("-fx-font-size: 10; -fx-text-fill: #666; -fx-padding: 2 12 4 12;");
VBox box = new VBox(subBar, audioTable, hint);
@@ -243,6 +291,9 @@ public class LocalizationEditorView extends BorderPane {
}
tableData.clear();
currentBundle.getEntries().forEach((k, v) -> tableData.add(new String[]{k, v}));
knownKeys = new HashSet<>(TextKeyStore.getKeys());
TextRegistry.clear();
TextRegistry.registerAll(currentBundle.getEntries());
@@ -254,7 +305,7 @@ public class LocalizationEditorView extends BorderPane {
private void addLanguage() {
TextInputDialog dlg = new TextInputDialog();
dlg.initOwner(Dialogs.primaryWindow());
dlg.setTitle("Sprache hinzufuegen");
dlg.setTitle("Sprache hinzufügen");
dlg.setHeaderText("Sprach-Code (z. B. de, en, fr):");
dlg.showAndWait().ifPresent(lang -> {
lang = lang.trim().toLowerCase();
@@ -274,7 +325,7 @@ public class LocalizationEditorView extends BorderPane {
String lang = langCombo.getValue();
if (lang == null) return;
Alert confirm = Dialogs.alert(Alert.AlertType.CONFIRMATION,
"Sprache '" + lang + "' wirklich loeschen?", ButtonType.YES, ButtonType.NO);
"Sprache '" + lang + "' wirklich löschen?", ButtonType.YES, ButtonType.NO);
confirm.showAndWait().ifPresent(bt -> {
if (bt == ButtonType.YES) {
try {
@@ -296,23 +347,25 @@ public class LocalizationEditorView extends BorderPane {
private void addKey() {
if (currentBundle == null) return;
Set<String> existing = currentBundle.getEntries().keySet();
List<String> available = new ArrayList<>(TextKeyStore.getKeys());
available.removeAll(existing);
Set<String> existing = new HashSet<>();
for (String[] row : tableData) existing.add(row[0]);
List<String> available = TextKeyStore.getKeys().stream()
.filter(k -> !existing.contains(k))
.collect(Collectors.toList());
if (available.isEmpty()) {
TextInputDialog dlg = new TextInputDialog();
dlg.initOwner(Dialogs.primaryWindow());
dlg.setTitle("Schluessel hinzufuegen");
dlg.setHeaderText("Alle bekannten Schluessel bereits vorhanden.\nNeuen Schluessel eingeben:");
dlg.setTitle("Schlüssel hinzufügen");
dlg.setHeaderText("Alle bekannten Schlüssel bereits vorhanden.\nNeuen Schlüssel eingeben:");
dlg.showAndWait().map(String::trim).filter(s -> !s.isBlank()).ifPresent(this::insertKey);
return;
}
Dialog<String> dlg = new Dialog<>();
dlg.initOwner(Dialogs.primaryWindow());
dlg.setTitle("Schluessel waehlen");
dlg.setHeaderText("Noch nicht uebersetzte Schluessel:");
dlg.setTitle("Schlüssel wählen");
dlg.setHeaderText("Noch nicht übersetzte Schlüssel:");
dlg.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
ObservableList<String> items = FXCollections.observableArrayList(available);
@@ -335,8 +388,14 @@ public class LocalizationEditorView extends BorderPane {
private void insertKey(String key) {
tableData.add(new String[]{key, ""});
table.scrollTo(tableData.size() - 1);
table.getSelectionModel().select(tableData.size() - 1);
// Scroll zum neuen Eintrag — in filteredData nach Index suchen
for (int i = 0; i < filteredData.size(); i++) {
if (filteredData.get(i)[0].equals(key)) {
table.scrollTo(i);
table.getSelectionModel().select(i);
break;
}
}
scheduleSave();
}
@@ -345,13 +404,36 @@ public class LocalizationEditorView extends BorderPane {
if (sel != null) { tableData.remove(sel); scheduleSave(); }
}
private void fillMissingKeys() {
Set<String> existing = new HashSet<>();
for (String[] row : tableData) existing.add(row[0]);
List<String> toAdd = TextKeyStore.getKeys().stream()
.filter(k -> !existing.contains(k))
.sorted()
.collect(Collectors.toList());
for (String k : toAdd) tableData.add(new String[]{k, ""});
if (!toAdd.isEmpty()) {
tableData.sort(Comparator.comparing(r -> r[0]));
knownKeys = new HashSet<>(TextKeyStore.getKeys());
scheduleSave();
}
Dialogs.alert(Alert.AlertType.INFORMATION,
toAdd.isEmpty()
? "Alle bekannten Schlüssel sind bereits vorhanden."
: toAdd.size() + " Schlüssel ergänzt.",
ButtonType.OK).showAndWait();
}
// Audio-Eintrags-Verwaltung
private void addAudioKey() {
TextInputDialog dlg = new TextInputDialog();
dlg.initOwner(Dialogs.primaryWindow());
dlg.setTitle("Audio-Schluessel hinzufuegen");
dlg.setHeaderText("Schluessel eingeben (muss dem AudioReference-Key im Dialog entsprechen):");
dlg.setTitle("Audio-Schlüssel hinzufügen");
dlg.setHeaderText("Schlüssel eingeben (muss dem AudioReference-Key im Dialog entsprechen):");
dlg.showAndWait().map(String::trim).filter(s -> !s.isBlank()).ifPresent(key -> {
audioData.add(new String[]{key, ""});
audioTable.scrollTo(audioData.size() - 1);
@@ -382,8 +464,8 @@ public class LocalizationEditorView extends BorderPane {
scheduleSave();
}
Dialogs.alert(Alert.AlertType.INFORMATION,
added.isEmpty() ? "Alle Dialog-Schluessel bereits vorhanden."
: added.size() + " Schluessel hinzugefuegt.",
added.isEmpty() ? "Alle Dialog-Schlüssel bereits vorhanden."
: added.size() + " Schlüssel hinzugefügt.",
ButtonType.OK).showAndWait();
}
@@ -407,7 +489,7 @@ public class LocalizationEditorView extends BorderPane {
private void saveCurrent() {
if (currentBundle == null) {
Dialogs.alert(Alert.AlertType.WARNING, "Keine Sprache ausgewaehlt.", ButtonType.OK).showAndWait();
Dialogs.alert(Alert.AlertType.WARNING, "Keine Sprache ausgewählt.", ButtonType.OK).showAndWait();
return;
}
Map<String, String> textEntries = new LinkedHashMap<>();