diff --git a/internal/langdetect/detect.go b/internal/langdetect/detect.go new file mode 100644 index 0000000..976ddf1 --- /dev/null +++ b/internal/langdetect/detect.go @@ -0,0 +1,77 @@ +package langdetect + +import ( + "strings" + "unicode" +) + +// Detect erkennt die Sprache eines Ansage-Texts und liefert "de" oder "en". +// Default (auch bei leerem oder unentscheidbarem Text) ist "de". +// +// Heuristik: Common-Word-Listen deutscher und englischer Wörter plus +// Umlaute/ß als starke Deutsch-Signale. Englisch gewinnt nur bei strikt +// mehr englischen Signalen als deutschen; bei Gleichstand entscheidet der +// Default "de". Die Funktion ist rein (kein globaler Zustand, keine IO). +func Detect(text string) string { + var de, en int + for _, r := range text { + switch r { + case 'ä', 'ö', 'ü', 'ß', 'Ä', 'Ö', 'Ü': + de++ + } + } + + for _, word := range strings.FieldsFunc(text, func(r rune) bool { + return !unicode.IsLetter(r) && r != '\'' && r != '’' + }) { + word = strings.ToLower(word) + if _, ok := germanWords[word]; ok { + de++ + } + if _, ok := englishWords[word]; ok { + en++ + } + } + + if en > de { + return "en" + } + return "de" +} + +// germanWords sind häufige deutsche Wörter, die im Englischen nicht +// vorkommen (Mehrdeutigkeiten wie „was" und „in" wurden weggelassen). +var germanWords = map[string]struct{}{ + "der": {}, "die": {}, "das": {}, "und": {}, "nicht": {}, "ist": {}, + "ein": {}, "eine": {}, "einen": {}, "einem": {}, "einer": {}, + "mit": {}, "für": {}, "auf": {}, "von": {}, "vom": {}, "zum": {}, + "zur": {}, "den": {}, "dem": {}, "des": {}, "wir": {}, "ihr": {}, + "sie": {}, "auch": {}, "als": {}, "wie": {}, "bei": {}, + "aus": {}, "nach": {}, "noch": {}, "nur": {}, "schon": {}, + "wenn": {}, "dann": {}, "aber": {}, "oder": {}, "doch": {}, + "mehr": {}, "sehr": {}, "über": {}, "unter": {}, "jetzt": {}, + "hier": {}, "heute": {}, "morgen": {}, "danke": {}, "bitte": {}, + "erfolgreich": {}, "fehlgeschlagen": {}, "fertig": {}, + "gestartet": {}, "beendet": {}, "abgeschlossen": {}, "warten": {}, + "läuft": {}, "kannst": {}, "muss": {}, "soll": {}, + "kein": {}, "keine": {}, "wieder": {}, "werden": {}, "wurde": {}, + "wurden": {}, "haben": {}, "geht": {}, "machen": {}, +} + +// englishWords sind häufige englische Wörter, die im Deutschen nicht +// vorkommen. +var englishWords = map[string]struct{}{ + "the": {}, "is": {}, "not": {}, "and": {}, "with": {}, "for": {}, + "on": {}, "of": {}, "to": {}, "from": {}, "by": {}, "at": {}, + "or": {}, "but": {}, "if": {}, "then": {}, "when": {}, "how": {}, + "what": {}, "why": {}, "who": {}, "done": {}, "failed": {}, + "finished": {}, "started": {}, "completed": {}, "error": {}, + "warning": {}, "thanks": {}, "you": {}, "your": {}, + "we": {}, "it": {}, "this": {}, "that": {}, "these": {}, + "those": {}, "was": {}, "were": {}, "are": {}, "be": {}, + "been": {}, "has": {}, "have": {}, "had": {}, "can": {}, + "will": {}, "would": {}, "should": {}, "could": {}, "just": {}, + "only": {}, "still": {}, "already": {}, "now": {}, "today": {}, + "tomorrow": {}, "waiting": {}, "running": {}, "please": {}, + "needs": {}, "need": {}, "succeeded": {}, +} diff --git a/internal/langdetect/detect_test.go b/internal/langdetect/detect_test.go new file mode 100644 index 0000000..8ad16a4 --- /dev/null +++ b/internal/langdetect/detect_test.go @@ -0,0 +1,37 @@ +package langdetect + +import "testing" + +// TestDetect ist tischgesteuert: Eingabetext → erwartete Sprache. +// Der Default "de" gilt für leeren, unentscheidbaren und gemischten +// Text ohne englische Mehrheit. +func TestDetect(t *testing.T) { + cases := []struct { + name string + text string + want string + }{ + {"leer", "", "de"}, + {"nur whitespace", " \n\t ", "de"}, + {"nur interpunktion", "!!! ??? ...", "de"}, + {"deutsch satz", "Der Build wurde erfolgreich abgeschlossen und das Deployment ist fertig.", "de"}, + {"deutsch umlaute", "Größe früh Ärzteübung — schön daß es klappt", "de"}, + {"ß alleine", "weiß", "de"}, + {"englisch satz", "The build has finished and the deployment is done.", "en"}, + {"englisch imperativ", "Please wait until the tests are green.", "en"}, + {"englisch kurz", "done", "en"}, + {"gemischt englisch dominiert", "the build is done and ready for you", "en"}, + {"gemischt deutsch dominiert", "Der build ist done und fertig für dich", "de"}, + {"gemischt gleichstand", "der the", "de"}, + {"unklare sprache", "Build 2026-10-18 14:22 UTC", "de"}, + {"agenten-typisch en", "Task completed. Please review the changes and approve.", "en"}, + {"agenten-typisch de", "Aufgabe abgeschlossen. Bitte prüfen und freigeben.", "de"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if got := Detect(tc.text); got != tc.want { + t.Errorf("Detect(%q) = %q, want %q", tc.text, got, tc.want) + } + }) + } +} diff --git a/internal/langdetect/doc.go b/internal/langdetect/doc.go index fe56788..205159d 100644 --- a/internal/langdetect/doc.go +++ b/internal/langdetect/doc.go @@ -1,4 +1,4 @@ // Package langdetect erkennt die Sprache eines Ansage-Texts als reine // Funktion Detect(text) → "de" | "en" (Default "de"; Heuristik über -// Common-Word-Listen und Umlaute/ß). Implementierung folgt in Task 02. +// Common-Word-Listen und Umlaute/ß). package langdetect