strings.ToTitle ist eine der am häufigsten missverstandenen Funktionen im strings-Paket. Der Name suggeriert, dass die Funktion einen String in „Title Case" konvertiert — also den ersten Buchstaben jedes Wortes großschreibt, wie es englische Buchüberschriften vorschreiben. Genau das tut sie aber nicht. ToTitle wendet das Unicode-Title-Case-Mapping auf jede einzelne Rune an. Für die meisten Buchstaben ist dieses Mapping identisch mit dem Upper-Case-Mapping — daher liefert ToTitle("hello world") schlicht HELLO WORLD, nicht Hello World.
Der praktische Unterschied zu ToUpper zeigt sich nur bei einer Handvoll Unicode-Digraphen wie dž (dz), die ein eigenes Title-Case-Glyph Dž (Dz) besitzen — getrennt vom Upper-Case DŽ (DZ). Wer tatsächlich „erster Buchstabe jedes Wortes groß" sucht, war früher bei der heute deprecated Funktion strings.Title richtig — und ist seit Go 1.18 auf golang.org/x/text/cases verwiesen. Diese Seite räumt mit dem Missverständnis auf und zeigt beide Wege sauber getrennt.
Die Funktion lebt im Standardpaket strings und hat eine denkbar einfache Signatur. Sie nimmt einen String entgegen, durchläuft ihn rune-weise und gibt einen neuen String zurück, in dem jede Rune durch ihre Title-Case-Variante ersetzt wurde. Es findet keine Wort- oder Wortgrenzen-Erkennung statt — der Algorithmus arbeitet rein auf Zeichenebene.
Das ist wichtig zu verinnerlichen, weil der Funktionsname genau diese falsche Erwartung weckt. Wer das einmal verstanden hat, nutzt ToTitle korrekt: als rune-weises Mapping mit ganz speziellem Verhalten für eine kleine Gruppe von Unicode-Codepoints.
package main
import (
"fmt"
"strings"
)
func main() {
// Signatur: func ToTitle(s string) string
s := "hello, gopher!"
fmt.Printf("%q\n", strings.ToTitle(s))
}"HELLO, GOPHER!"Wie man sieht: keine Spur von „Hello, Gopher!" — stattdessen vollständig großgeschrieben, exakt wie strings.ToUpper es liefern würde. Das ist kein Bug, sondern das dokumentierte Verhalten.
Der einzige sichtbare Unterschied zwischen ToTitle und ToUpper zeigt sich bei Unicode-Digraphen. Ein Digraph ist eine Buchstabenkombination, die als ein einzelner Codepoint kodiert ist — etwa der kroatische Digraph dž (U+01C6) oder das niederländische ij. Für solche Zeichen kennt Unicode drei Formen: lowercase (dž), titlecase (Dž) und uppercase (DŽ).
Beim Titlecase ist nur der erste Bestandteil des Digraphen groß — beim Uppercase beide. Das ist sinnvoll, wenn ein Digraph am Wortanfang steht und der Rest des Wortes kleingeschrieben bleibt: man möchte nicht DZenan, sondern Dzenan (technisch Dženan).
package main
import (
"fmt"
"strings"
)
func main() {
// U+01C6 ist das Lowercase-dz-Digraph
s := "dženan"
fmt.Printf("Original: %s\n", s)
fmt.Printf("ToUpper: %s\n", strings.ToUpper(s))
fmt.Printf("ToTitle: %s\n", strings.ToTitle(s))
}Original: dženan
ToUpper: DŽENAN
ToTitle: DžENANBeachte den Unterschied an Position 1: DŽ (vollständig groß) bei ToUpper, Dž (gemischt) bei ToTitle. Ab Position 2 verhalten sich beide gleich — alle weiteren Runen werden in ihre Großform überführt. Das ist das gesamte praktische Delta zwischen den beiden Funktionen.
Die naheliegende Frage lautet: warum gibt es keinen Schalter, der bei jedem Wortanfang Titlecase anwendet und sonst Lowercase belässt? Die Antwort liegt in der inhärenten Schwierigkeit der Aufgabe. „Wortanfang" ist sprachabhängig — englische Konventionen (kleine Wörter wie „the", „of" bleiben klein) unterscheiden sich von deutschen, französischen, türkischen. Whitespace allein reicht nicht als Wortgrenze; Bindestriche, Apostrophe und Unicode-Wortbrecher spielen mit rein.
Genau deshalb hat sich das Go-Team entschieden, diese Logik aus dem Standardpaket zu entfernen und in das spezialisierte golang.org/x/text/cases-Paket auszulagern, das eine sprachsensitive Implementierung anbietet. Im strings-Paket bleibt nur das mechanische Rune-Mapping.
package main
import (
"fmt"
"strings"
)
func main() {
// Die intuitive Erwartung
got := strings.ToTitle("der gopher hüpft")
fmt.Printf("Bekommen: %q\n", got)
fmt.Println("Erwartet vielleicht: „Der Gopher Hüpft\"")
}Bekommen: "DER GOPHER HÜPFT"
Erwartet vielleicht: „Der Gopher Hüpft"Wer ToTitle für die zweite Variante verwenden will, wird enttäuscht. Die Funktion ist schlicht das falsche Werkzeug.
Bis Go 1.17 gab es im strings-Paket die Funktion strings.Title, die genau das tat, was viele von ToTitle erwarten: jeden Wortanfang großschreiben. Mit Go 1.18 wurde sie als deprecated markiert. Der Grund: die Implementierung war zu simpel — sie ignorierte Sprachregeln, behandelte alle Whitespace-Codepoints gleich als Wortgrenze und produzierte für viele Sprachen falsche Ergebnisse (etwa beim türkischen i-Sonderfall).
Statt eine fehlerhafte Funktion im Standard zu pflegen, hat das Team sie aus dem Wartungsfokus genommen und in die offizielle Doku den Verweis auf golang.org/x/text/cases aufgenommen. Die alte Funktion existiert noch — Go bricht keine Rückwärtskompatibilität — aber neue Codebasen sollten sie meiden.
package main
import (
"fmt"
"strings"
)
func main() {
// strings.Title funktioniert noch, ist aber deprecated.
// go vet und Linter warnen darauf hin.
fmt.Println(strings.Title("der schnelle gopher"))
}Der Schnelle GopherDas Ergebnis sieht oberflächlich richtig aus — aber strings.Title macht jedes Wort groß, ohne kleine Wörter („der") wie in englischer Titelkonvention auszunehmen, und kennt keine sprachspezifischen Regeln. Für ernsthafte Anwendungen ist das zu grob.
Das Paket golang.org/x/text/cases liefert Case-Mapping mit Spracheinstellung. Über cases.Title(language.German) bekommt man einen Caser, der mit String(s) aufgerufen wird und sprachsensitiv arbeitet. Der Caser lässt sich wiederverwenden — er ist threadsicher und allokationsfreundlich.
Das Paket muss explizit installiert werden, da es außerhalb der Standardbibliothek liegt: go get golang.org/x/text. Für jedes Projekt, das ernsthaft Texte für die Anzeige formatiert, lohnt sich diese Abhängigkeit.
package main
import (
"fmt"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
func main() {
titleDE := cases.Title(language.German)
titleEN := cases.Title(language.English)
s := "der schnelle braune fuchs"
fmt.Println("DE:", titleDE.String(s))
fmt.Println("EN:", titleEN.String(s))
}DE: Der Schnelle Braune Fuchs
EN: Der Schnelle Braune FuchsIm trivialen Beispiel sehen DE und EN gleich aus — der Unterschied wird bei Sonderzeichen, Akzenten und Sprachen mit echten Casing-Eigenheiten (Türkisch, Litauisch) sichtbar. Wichtig: cases.Title macht standardmäßig jedes Wort groß, kennt also keine Stop-Word-Liste; wer englische Buchtitel-Konvention will, muss zusätzlich filtern.
Wenn man bei strings.ToTitle bleiben will, aber für eine spezifische Sprache angepasstes Mapping braucht (etwa Türkisch, wo i und ı unterschiedliche Großbuchstaben haben), gibt es strings.ToTitleSpecial. Sie nimmt einen unicode.SpecialCase als ersten Parameter und wendet dessen Sonderregeln an.
Das ändert nichts daran, dass die Funktion rune-weise arbeitet — sie macht weiterhin nicht „Jedes Wort groß". Sie ersetzt nur das Standard-Title-Mapping durch ein sprachspezifisches.
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
// Türkisch: dotted i -> dotted İ, dotless ı -> dotless I
s := "istanbul ışık"
std := strings.ToTitle(s)
tr := strings.ToTitleSpecial(unicode.TurkishCase, s)
fmt.Printf("Standard: %q\n", std)
fmt.Printf("Türkisch: %q\n", tr)
}Standard: "ISTANBUL IŞIK"
Türkisch: "İSTANBUL IŞIK"Im Standard-Mapping wird i zu I, im türkischen Mapping korrekt zu İ (mit Punkt). Das ist das einzige Szenario, in dem ToTitleSpecial gegenüber ToUpperSpecial einen sichtbaren Mehrwert bringt — und auch nur für Digraphen oder spezielle SpecialCase-Tabellen.
Wer aus didaktischen Gründen oder in einem Mini-Tool ohne externe Abhängigkeit „erster Buchstabe jedes Wortes groß" nachbauen will, kommt um manuelle Logik nicht herum. Die folgende Funktion zeigt das Prinzip: durch die Runen iterieren, beim Übergang von Whitespace zu Nicht-Whitespace die Rune in Title-Case wandeln, sonst belassen.
Sie ist absichtlich naiv — sie kennt keine sprachspezifischen Regeln, keine Apostrophe und keine Stop-Words. Für Produktivcode bleibt cases.Title die richtige Wahl. Aber als Lernbeispiel klärt der Code, was unter „Wortanfang großschreiben" überhaupt operationell zu verstehen ist.
package main
import (
"fmt"
"strings"
"unicode"
)
// WordTitle schreibt den ersten Buchstaben jedes Wortes groß.
// Naive Implementation — Whitespace als einzige Wortgrenze.
func WordTitle(s string) string {
var b strings.Builder
b.Grow(len(s))
prevSpace := true
for _, r := range s {
if prevSpace && !unicode.IsSpace(r) {
b.WriteRune(unicode.ToTitle(r))
} else {
b.WriteRune(r)
}
prevSpace = unicode.IsSpace(r)
}
return b.String()
}
func main() {
fmt.Println(WordTitle("der schnelle braune fuchs"))
fmt.Println(WordTitle("dženan kommt aus zagreb"))
}Der Schnelle Braune Fuchs
Dženan Kommt Aus ZagrebIm zweiten Beispiel sieht man den Witz von unicode.ToTitle: das dž am Wortanfang wird zu Dž — nicht zu DŽ. Wer hier unicode.ToUpper verwendet hätte, hätte den Digraph komplett großgeschrieben. Für Wortanfänge ist ToTitle also die typografisch saubere Wahl.
Echte englische Buchtitel-Konvention („Title Case" im typografischen Sinn) verlangt, dass kleine Funktionswörter wie „a", „an", „the", „of", „in" klein bleiben — außer am Satzanfang. Weder strings.ToTitle noch cases.Title machen das von sich aus. Wer es braucht, kombiniert cases.Title mit einer Stop-Word-Liste.
Das folgende Beispiel zeigt eine kompakte Implementierung: zuerst cases.Title auf den gesamten String, dann splitten und Stop-Words zurück in Kleinschreibung — außer dem ersten Wort, das immer groß bleibt. Für mehr Sprachen oder feinere Regeln (gekürzte Verben, Eigennamen) wächst die Logik schnell, aber das Grundgerüst ist klar.
package main
import (
"fmt"
"strings"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
var stopWords = map[string]bool{
"a": true, "an": true, "the": true,
"and": true, "or": true, "but": true,
"of": true, "in": true, "on": true, "at": true, "to": true,
}
func BookTitle(s string) string {
titled := cases.Title(language.English).String(s)
words := strings.Fields(titled)
for i := 1; i < len(words); i++ {
lower := strings.ToLower(words[i])
if stopWords[lower] {
words[i] = lower
}
}
return strings.Join(words, " ")
}
func main() {
fmt.Println(BookTitle("the lord of the rings"))
fmt.Println(BookTitle("a tale of two cities"))
}The Lord of the Rings
A Tale of Two CitiesBeachte: das erste Wort bleibt großgeschrieben („The", „A"), auch wenn es ein Stop-Word ist. Das entspricht der typografischen Konvention. Die Implementierung ist kurz und zeigt, wie man cases.Title als Baustein in eigene Logik einbaut, statt sich auf ein magisches strings.X zu verlassen, das es nicht gibt.
Mapping pro Rune, nicht pro Wort
strings.ToTitle wendet Unicode-Title-Case-Mapping auf jede einzelne Rune an — es erkennt keine Wortgrenzen.
Meist identisch zu ToUpper
Für die überwältigende Mehrheit der Zeichen liefert ToTitle dasselbe Ergebnis wie ToUpper.
Digraphen als sichtbarer Unterschied
Nur bei Unicode-Digraphen (dž → Dž statt DŽ) wird der Unterschied zwischen Title- und Upper-Case sichtbar.
strings.Title ist deprecated
Seit Go 1.18 markiert; war zu simpel und sprachunsensitiv. Neuer Code sollte sie meiden.
cases.Title für Wort-Titlecase
golang.org/x/text/cases.Title(language.X) ist die offiziell empfohlene Lösung für „jedes Wort groß".
ToTitleSpecial für Sprachregeln
strings.ToTitleSpecial(unicode.TurkishCase, s) wendet sprachspezifisches Title-Mapping an — weiterhin rune-weise.
Stop-Words sind Eigenlogik
Englische Buchtitel-Konvention („the", „of" klein) ist kein eingebautes Feature — man baut sie auf cases.Title drauf.
unicode.ToTitle für einzelne Runen
Für einzelne Runen liefert unicode.ToTitle(r) die korrekte Title-Form — nützlich beim Bau eigener WordTitle-Funktionen.
Weiterführende Ressourcen
Externe Quellen
strings.ToTitlestrings.ToTitleSpecialstrings.Title(deprecated)golang.org/x/text/cases- Unicode Technical Standard #21 — Case Mappings