#88 + #77: Fehlerursache-Übersetzung und vollständige Tooltip-Abdeckung

Aufgabe 1 (#88): AiFailureMessageTranslator auf public gesetzt, damit der
Verlauf-Tab die technischen Fehlermeldungen in benutzerfreundliche deutsche
Texte übersetzen kann.

Aufgabe 2 (#77): Vollständige Bestandsaufnahme aller interaktiven GUI-Elemente.
13 neue Konstanten in GuiTooltipTexts ergänzt (Provider-Felder, Verarbeitungs-
limits, optionale Pfade, Vorschau-Navigation, Prompt-Buttons, Dateiname-Textfeld).
Alle fehlenden Tooltips in GuiConfigurationEditorWorkspace, GuiPromptEditorTab,
PdfPreviewPane und FileNameEditorPane gesetzt. Hartcodierte Strings in
GuiPromptEditorTab durch Konstantenreferenzen ersetzt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-05 13:25:56 +02:00
parent 6c2e2efe22
commit 0412874f08
7 changed files with 87 additions and 9 deletions
@@ -1573,10 +1573,12 @@ public final class GuiConfigurationEditorWorkspace {
TextField lockField = boundTextField( TextField lockField = boundTextField(
editorState.values().runtimeLockFile(), editorState.values().runtimeLockFile(),
val -> updateValues(editorState.values().withRuntimeLockFile(val))); val -> updateValues(editorState.values().withRuntimeLockFile(val)));
applyTooltip(lockField, GuiTooltipTexts.PFADE_LOCK_DATEI);
TextField logDirField = boundTextField( TextField logDirField = boundTextField(
editorState.values().logDirectory(), editorState.values().logDirectory(),
val -> updateValues(editorState.values().withLogDirectory(val))); val -> updateValues(editorState.values().withLogDirectory(val)));
applyTooltip(logDirField, GuiTooltipTexts.PFADE_LOG_VERZEICHNIS);
VBox optionalContent = new VBox(4); VBox optionalContent = new VBox(4);
optionalContent.setPadding(new Insets(6, 0, 0, 0)); optionalContent.setPadding(new Insets(6, 0, 0, 0));
@@ -1858,6 +1860,7 @@ public final class GuiConfigurationEditorWorkspace {
val, pState2.model(), pState2.timeoutSeconds(), pState2.apiKey()))); val, pState2.model(), pState2.timeoutSeconds(), pState2.apiKey())));
Label baseUrlError = createFieldErrorLabel(); Label baseUrlError = createFieldErrorLabel();
fieldErrorLabels.put(ns + "baseUrl", baseUrlError); fieldErrorLabels.put(ns + "baseUrl", baseUrlError);
applyTooltip(baseUrlField, GuiTooltipTexts.PROVIDER_BASIS_URL);
HBox baseUrlBox = new HBox(4, baseUrlField); HBox baseUrlBox = new HBox(4, baseUrlField);
HBox.setHgrow(baseUrlField, Priority.ALWAYS); HBox.setHgrow(baseUrlField, Priority.ALWAYS);
fieldGrid.add(new Label("Basis-URL:"), 0, gridRow); fieldGrid.add(new Label("Basis-URL:"), 0, gridRow);
@@ -1866,6 +1869,7 @@ public final class GuiConfigurationEditorWorkspace {
TextField timeoutField = boundTextField(pState.timeoutSeconds(), TextField timeoutField = boundTextField(pState.timeoutSeconds(),
val -> updateProviderField(family, pState2 -> new GuiProviderConfigurationState( val -> updateProviderField(family, pState2 -> new GuiProviderConfigurationState(
pState2.baseUrl(), pState2.model(), val, pState2.apiKey()))); pState2.baseUrl(), pState2.model(), val, pState2.apiKey())));
applyTooltip(timeoutField, GuiTooltipTexts.PROVIDER_TIMEOUT);
Label timeoutError = createFieldErrorLabel(); Label timeoutError = createFieldErrorLabel();
fieldErrorLabels.put(ns + "timeoutSeconds", timeoutError); fieldErrorLabels.put(ns + "timeoutSeconds", timeoutError);
fieldGrid.add(new Label("Timeout (Sek.):"), 2, gridRow); fieldGrid.add(new Label("Timeout (Sek.):"), 2, gridRow);
@@ -1924,6 +1928,7 @@ public final class GuiConfigurationEditorWorkspace {
val -> updateProviderField(family, pState2 -> new GuiProviderConfigurationState( val -> updateProviderField(family, pState2 -> new GuiProviderConfigurationState(
pState2.baseUrl(), pState2.model(), pState2.timeoutSeconds(), pState2.baseUrl(), pState2.model(), pState2.timeoutSeconds(),
GuiProviderApiKeyState.unresolved(val)))); GuiProviderApiKeyState.unresolved(val))));
applyTooltip(apiKeyField, GuiTooltipTexts.PROVIDER_API_KEY);
Label apiKeyError = createFieldErrorLabel(); Label apiKeyError = createFieldErrorLabel();
fieldErrorLabels.put(ns + "apiKey", apiKeyError); fieldErrorLabels.put(ns + "apiKey", apiKeyError);
Label apiKeyOriginLabel = createApiKeyOriginLabel(); Label apiKeyOriginLabel = createApiKeyOriginLabel();
@@ -2015,6 +2020,7 @@ public final class GuiConfigurationEditorWorkspace {
TextField maxRetriesField = boundTextField( TextField maxRetriesField = boundTextField(
editorState.values().maxRetriesTransient(), editorState.values().maxRetriesTransient(),
val -> updateValues(editorState.values().withMaxRetriesTransient(val))); val -> updateValues(editorState.values().withMaxRetriesTransient(val)));
applyTooltip(maxRetriesField, GuiTooltipTexts.LIMITS_MAX_RETRIES);
grid.add(new Label("Max. Retries:"), 2, row); grid.add(new Label("Max. Retries:"), 2, row);
grid.add(maxRetriesField, 3, row); grid.add(maxRetriesField, 3, row);
row++; row++;
@@ -2023,6 +2029,7 @@ public final class GuiConfigurationEditorWorkspace {
TextField logLevelField = boundTextField( TextField logLevelField = boundTextField(
editorState.values().logLevel(), editorState.values().logLevel(),
val -> updateValues(editorState.values().withLogLevel(val))); val -> updateValues(editorState.values().withLogLevel(val)));
applyTooltip(logLevelField, GuiTooltipTexts.LIMITS_LOG_LEVEL);
grid.add(new Label("Log-Level:"), 0, row); grid.add(new Label("Log-Level:"), 0, row);
grid.add(logLevelField, 1, row); grid.add(logLevelField, 1, row);
@@ -2032,6 +2039,7 @@ public final class GuiConfigurationEditorWorkspace {
sensitiveCheck.setSelected(sensitive); sensitiveCheck.setSelected(sensitive);
sensitiveCheck.selectedProperty().addListener((obs, oldVal, newVal) -> sensitiveCheck.selectedProperty().addListener((obs, oldVal, newVal) ->
updateValues(editorState.values().withLogAiSensitive(Boolean.toString(newVal)))); updateValues(editorState.values().withLogAiSensitive(Boolean.toString(newVal))));
applyTooltip(sensitiveCheck, GuiTooltipTexts.LIMITS_SENSIBLE_KI_AUSGABE);
grid.add(new Label(), 2, row); grid.add(new Label(), 2, row);
grid.add(sensitiveCheck, 3, row); grid.add(sensitiveCheck, 3, row);
@@ -8,7 +8,6 @@ import java.util.function.Function;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiTooltipTexts;
import de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingFailure; import de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingFailure;
import de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingSuccess; import de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingSuccess;
import de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult; import de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult;
@@ -245,14 +244,13 @@ public class GuiPromptEditorTab {
statusLabel.setStyle("-fx-text-fill: #555555;"); statusLabel.setStyle("-fx-text-fill: #555555;");
// Buttons verdrahten // Buttons verdrahten
saveButton.setTooltip(new Tooltip("Prompt-Datei speichern (atomar, UTF-8).")); saveButton.setTooltip(new Tooltip(GuiTooltipTexts.PROMPT_SPEICHERN));
saveButton.setOnAction(e -> requestSave()); saveButton.setOnAction(e -> requestSave());
resetButton.setTooltip(new Tooltip("Textfeld mit dem Standard-Prompt-Inhalt befüllen, ohne zu speichern.")); resetButton.setTooltip(new Tooltip(GuiTooltipTexts.PROMPT_ZURUECKSETZEN));
resetButton.setOnAction(e -> resetToDefault()); resetButton.setOnAction(e -> resetToDefault());
createDefaultButton.setTooltip(new Tooltip( createDefaultButton.setTooltip(new Tooltip(GuiTooltipTexts.PROMPT_STANDARD_ANLEGEN));
"Standard-Prompt-Datei am konfigurierten Pfad anlegen."));
createDefaultButton.setOnAction(e -> requestCreateDefault()); createDefaultButton.setOnAction(e -> requestCreateDefault());
createDefaultButton.setVisible(false); createDefaultButton.setVisible(false);
createDefaultButton.setManaged(false); createDefaultButton.setManaged(false);
@@ -65,6 +65,14 @@ public final class GuiTooltipTexts {
public static final String PFADE_PROMPT = public static final String PFADE_PROMPT =
"Externe Textdatei mit den KI-Anweisungen."; "Externe Textdatei mit den KI-Anweisungen.";
/** Tooltip für das Eingabefeld „Lock-Datei". */
public static final String PFADE_LOCK_DATEI =
"Pfad zur Lock-Datei, die parallele Instanzen verhindert (optional).";
/** Tooltip für das Eingabefeld „Log-Verzeichnis". */
public static final String PFADE_LOG_VERZEICHNIS =
"Verzeichnis für Log-Dateien. Leer = Standardverzeichnis logs/ im Programmverzeichnis.";
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Konfigurationstab Provider // Konfigurationstab Provider
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@@ -77,6 +85,18 @@ public final class GuiTooltipTexts {
public static final String PROVIDER_MODELL = public static final String PROVIDER_MODELL =
"Das konkrete Sprachmodell des gewählten Providers."; "Das konkrete Sprachmodell des gewählten Providers.";
/** Tooltip für das Eingabefeld „Basis-URL". */
public static final String PROVIDER_BASIS_URL =
"Basis-URL des KI-Dienstes (z.B. https://api.openai.com/v1).";
/** Tooltip für das Eingabefeld „Timeout". */
public static final String PROVIDER_TIMEOUT =
"Zeitlimit für KI-Anfragen in Sekunden.";
/** Tooltip für das Eingabefeld „API-Key". */
public static final String PROVIDER_API_KEY =
"API-Schlüssel für den konfigurierten KI-Dienst. Umgebungsvariable hat Vorrang.";
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Konfigurationstab Verarbeitungslimits // Konfigurationstab Verarbeitungslimits
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@@ -93,10 +113,26 @@ public final class GuiTooltipTexts {
public static final String LIMITS_MAX_TITLE_LENGTH = public static final String LIMITS_MAX_TITLE_LENGTH =
"Maximale Länge des Dateinamens in Zeichen (ohne Datum und Erweiterung). Gültig: 10120."; "Maximale Länge des Dateinamens in Zeichen (ohne Datum und Erweiterung). Gültig: 10120.";
/** Tooltip für das Eingabefeld „max.retries.transient". */
public static final String LIMITS_MAX_RETRIES =
"Maximale Anzahl transienter Wiederholversuche je Dokument (Ganzzahl ≥ 1).";
/** Tooltip für das Eingabefeld „Log-Level". */
public static final String LIMITS_LOG_LEVEL =
"Log-Detailstufe (z. B. INFO, DEBUG, WARN). Leer = Standardwert INFO.";
/** Tooltip für die Checkbox „Sensible KI-Ausgabe". */
public static final String LIMITS_SENSIBLE_KI_AUSGABE =
"Vollständige KI-Antworten in die Log-Datei schreiben (nur für Diagnosezwecke empfohlen).";
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Verarbeitungslauf-Tab Dateiname-Editor // Verarbeitungslauf-Tab Dateiname-Editor
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
/** Tooltip für das Dateiname-Textfeld im Dateiname-Editor. */
public static final String DATEINAME_TEXTFELD =
"Dateiname bearbeiten. Format: JJJJ-MM-TT - Titel.pdf";
/** Tooltip für den Button „Dateiname übernehmen". */ /** Tooltip für den Button „Dateiname übernehmen". */
public static final String DATEINAME_UEBERNEHMEN = public static final String DATEINAME_UEBERNEHMEN =
"Benennt die Zieldatei um und aktualisiert die Datenbank. Nicht rückgängig zu machen."; "Benennt die Zieldatei um und aktualisiert die Datenbank. Nicht rückgängig zu machen.";
@@ -133,6 +169,14 @@ public final class GuiTooltipTexts {
public static final String BATCHRUN_MESSAGE_AREA = public static final String BATCHRUN_MESSAGE_AREA =
"Statusmeldungen und Fortschrittsinformationen des aktuellen Verarbeitungslaufs."; "Statusmeldungen und Fortschrittsinformationen des aktuellen Verarbeitungslaufs.";
/** Tooltip für den Navigations-Button „Vorherige Seite" in der PDF-Vorschau. */
public static final String PREVIEW_VORHERIGE_SEITE =
"Vorherige Seite der Vorschau anzeigen.";
/** Tooltip für den Navigations-Button „Nächste Seite" in der PDF-Vorschau. */
public static final String PREVIEW_NAECHSTE_SEITE =
"Nächste Seite der Vorschau anzeigen.";
/** Tooltip für Spalte „Status" in der Verarbeitungslauf-Tabelle. */ /** Tooltip für Spalte „Status" in der Verarbeitungslauf-Tabelle. */
public static final String BATCHRUN_COL_STATUS = public static final String BATCHRUN_COL_STATUS =
"Verarbeitungsergebnis: Erfolg, Fehler oder übersprungen."; "Verarbeitungsergebnis: Erfolg, Fehler oder übersprungen.";
@@ -233,6 +277,18 @@ public final class GuiTooltipTexts {
public static final String PROMPT_TEXTAREA = public static final String PROMPT_TEXTAREA =
"KI-Anweisungstext. Dieser Prompt wird bei jedem Verarbeitungsversuch an das Sprachmodell gesendet."; "KI-Anweisungstext. Dieser Prompt wird bei jedem Verarbeitungsversuch an das Sprachmodell gesendet.";
/** Tooltip für den Button „Speichern" im Prompt-Editor-Tab. */
public static final String PROMPT_SPEICHERN =
"Prompt-Datei speichern (atomar, UTF-8).";
/** Tooltip für den Button „Auf Standard zurücksetzen" im Prompt-Editor-Tab. */
public static final String PROMPT_ZURUECKSETZEN =
"Textfeld mit dem Standard-Prompt-Inhalt befüllen, ohne zu speichern.";
/** Tooltip für den Button „Standard-Prompt erstellen" im Prompt-Editor-Tab. */
public static final String PROMPT_STANDARD_ANLEGEN =
"Standard-Prompt-Datei am konfigurierten Pfad anlegen.";
/** Nicht instanziierbar reine Konstantenklasse. */ /** Nicht instanziierbar reine Konstantenklasse. */
private GuiTooltipTexts() { private GuiTooltipTexts() {
throw new UnsupportedOperationException("Nicht instanziierbar"); throw new UnsupportedOperationException("Nicht instanziierbar");
@@ -2,7 +2,7 @@ package de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun;
/** /**
* Übersetzt strukturierte Fehlermeldungen aus der Anwendungsschicht in * Übersetzt strukturierte Fehlermeldungen aus der Anwendungsschicht in
* benutzerfreundliche deutsche Texte für den Detailbereich des Verarbeitungslauf-Tabs. * benutzerfreundliche deutsche Texte für die Darstellungsschicht der GUI.
* <p> * <p>
* Die Klasse wertet die englischsprachige Fehlermeldung aus dem Verarbeitungsversuch * Die Klasse wertet die englischsprachige Fehlermeldung aus dem Verarbeitungsversuch
* musterbasiert aus und liefert eine für den Endbenutzer lesbare Beschreibung des * musterbasiert aus und liefert eine für den Endbenutzer lesbare Beschreibung des
@@ -12,8 +12,10 @@ package de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun;
* Die Mustererkennung erfolgt ohne Berücksichtigung der Groß-/Kleinschreibung * Die Mustererkennung erfolgt ohne Berücksichtigung der Groß-/Kleinschreibung
* und prüft die definierten Schlüsselbegriffe in festgelegter Reihenfolge, * und prüft die definierten Schlüsselbegriffe in festgelegter Reihenfolge,
* damit spezifischere Muster vor allgemeineren greifen. * damit spezifischere Muster vor allgemeineren greifen.
* <p>
* Die Klasse wird sowohl im Verarbeitungslauf-Tab als auch im Verlauf-Tab verwendet.
*/ */
final class AiFailureMessageTranslator { public final class AiFailureMessageTranslator {
private AiFailureMessageTranslator() { private AiFailureMessageTranslator() {
} }
@@ -28,7 +30,7 @@ final class AiFailureMessageTranslator {
* @param technicalMessage die rohe technische Fehlermeldung; darf {@code null} sein * @param technicalMessage die rohe technische Fehlermeldung; darf {@code null} sein
* @return eine nicht-leere deutsche Benutzerfehlermeldung ohne führendes Warnsymbol * @return eine nicht-leere deutsche Benutzerfehlermeldung ohne führendes Warnsymbol
*/ */
static String translate(String technicalMessage) { public static String translate(String technicalMessage) {
if (technicalMessage == null || technicalMessage.isBlank()) { if (technicalMessage == null || technicalMessage.isBlank()) {
return "Verarbeitung fehlgeschlagen. Bitte Konfiguration prüfen und ggf. erneut verarbeiten."; return "Verarbeitung fehlgeschlagen. Bitte Konfiguration prüfen und ggf. erneut verarbeiten.";
} }
@@ -76,6 +76,9 @@ public final class FileNameEditorPane {
sectionTitle.setStyle("-fx-font-weight: bold;"); sectionTitle.setStyle("-fx-font-weight: bold;");
textField.setId("filename-editor-text-field"); textField.setId("filename-editor-text-field");
Tooltip textFieldTooltip = new Tooltip(GuiTooltipTexts.DATEINAME_TEXTFELD);
textFieldTooltip.setShowDelay(Duration.millis(300));
textField.setTooltip(textFieldTooltip);
HBox.setHgrow(textField, Priority.ALWAYS); HBox.setHgrow(textField, Priority.ALWAYS);
HBox inputRow = new HBox(4, textField); HBox inputRow = new HBox(4, textField);
@@ -22,10 +22,13 @@ import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Bounds; import javafx.geometry.Bounds;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiTooltipTexts;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator; import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.ScrollPane; import javafx.scene.control.ScrollPane;
import javafx.scene.control.Tooltip;
import javafx.util.Duration;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.input.ScrollEvent; import javafx.scene.input.ScrollEvent;
@@ -213,9 +216,15 @@ public final class PdfPreviewPane {
prevButton.setId("pdf-preview-prev-button"); prevButton.setId("pdf-preview-prev-button");
prevButton.setOnAction(e -> navigateToPreviousPage()); prevButton.setOnAction(e -> navigateToPreviousPage());
Tooltip prevTooltip = new Tooltip(GuiTooltipTexts.PREVIEW_VORHERIGE_SEITE);
prevTooltip.setShowDelay(Duration.millis(300));
prevButton.setTooltip(prevTooltip);
nextButton.setId("pdf-preview-next-button"); nextButton.setId("pdf-preview-next-button");
nextButton.setOnAction(e -> navigateToNextPage()); nextButton.setOnAction(e -> navigateToNextPage());
Tooltip nextTooltip = new Tooltip(GuiTooltipTexts.PREVIEW_NAECHSTE_SEITE);
nextTooltip.setShowDelay(Duration.millis(300));
nextButton.setTooltip(nextTooltip);
pageLabel.setId("pdf-preview-page-label"); pageLabel.setId("pdf-preview-page-label");
pageLabel.setStyle("-fx-text-fill: #555555;"); pageLabel.setStyle("-fx-text-fill: #555555;");
@@ -16,6 +16,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiTooltipTexts; import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiTooltipTexts;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.AiFailureMessageTranslator;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.ProcessingStatusPresentation; import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.ProcessingStatusPresentation;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord; import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord;
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt; import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
@@ -810,7 +811,8 @@ public final class GuiHistoryTab {
} }
} }
failureArea.setText(failureMessage != null ? failureMessage : ""); failureArea.setText(failureMessage != null
? AiFailureMessageTranslator.translate(failureMessage) : "");
failureArea.setPromptText("Keine Fehlerdetails gespeichert."); failureArea.setPromptText("Keine Fehlerdetails gespeichert.");
} }